AlexanderHart AlexanderHart - 3 months ago 9
Swift Question

Low res image taking too long to load

Using Facebook Graph API, I retrieved a string URL to a

200x200
profile picture that I want to display in a
UIImageView
. I'm successfully able to do this, but I notice that it can take as long as 10 seconds for the image to display on the screen. Can anyone give me some pointers (no pun intended) on how to optimize it?

override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)

NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: self.profilePictureUrl)!, completionHandler: { (data, response, error) ->
Void in
self.profilePictureImageView.image = UIImage(data: data!)
self.profilePictureImageView.layer.cornerRadius = self.profilePictureImageView.frame.size.width / 2;
self.profilePictureImageView.clipsToBounds = true

dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.view.addSubview(self.profilePictureImageView)
})

}).resume()
}

par par
Answer

You should move all UIView calls (so anything you set on the UIImageView) onto the main thread as UIKit for the most part isn't thread-safe. You can instantiate the UIImage on the background thread though for performance optimization, so try this:

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)

    let url = NSURL(string: self.profilePictureUrl)!

    NSURLSession.sharedSession().dataTaskWithURL(
        url,
        completionHandler: { [weak self] (data, response, error) -> Void in
            guard let strongSelf = self else { return }

            // create the UIImage on the background thread
            let image = UIImage(data: data!)

            // then jump to the main thread to modify your UIImageView
            dispatch_async(dispatch_get_main_queue(), { [weak self] () -> Void in
                guard let strongSelf = self else { return }

                let profilePictureImageView = strongSelf.profilePictureImageView

                profilePictureImageView.image = image
                profilePictureImageView.layer.cornerRadius = profilePictureImageView.frame.size.width / 2;
                profilePictureImageView.clipsToBounds = true

                strongSelf.view.addSubview(profilePictureImageView)
            })
        }
    ).resume()
}

Note also that I've weak-ified your references to self. There is no guarantee the user hasn't dismissed the view controller that is initiating this code by the time the completion routines get called so you want to make sure you're not keeping a strong reference to self. This allows the view controller to deallocate if the user dismisses it and the completion routines then return early without doing any unnecessary work.