Bad practice: loading a large image on the main thread


You’re more of a video kind of person? I’ve got you covered! Here’s a video with the same content than this article 🍿


Can you guess what’s the problem with this code?

We’re creating a UIImage and then we assign it to a UIImageView.

At first glance, there’s nothing special: you’ve probably written similar code dozens of time.

However, you can notice that I’m hinting at the fact that the image is quite large:

That’s a problem because loading a large image is an operation that takes some time to complete.

And with the way the code is currently written, this loading operation will take place on the main thread.

This means that the main thread will be blocked while the image loads…

So if this code is, for example, part of the configuration of a UICollectionViewCell, it could result in a scrolling animation that looks laggy and uncomfortable to the eye.

Of course, this is something that we definitely want to avoid!

The typical solution would be to offload the heavy work to a background thread.

But that’s not possible here: because UIImageView is a UIKit object, and so interacting with it from a background thread would result in a crash.

And that’s when the method prepareForDisplay can help you!

When we call this method on a UIImage, it will decode the image on a background thread, and then call its completionHandler once the decoded image is ready to be displayed 👌

Just don’t forget to switch back to the main thread before you set the preparedImage on the imageView!

As you can see, the method prepareForDisplay is very helpful and is not that hard to integrate.

And if you’re using Swift Concurrency, it will be even easier, because all you’ll need to do is call its async variant and the thread switching will be handled automatically!

That’s all for this article, I hope you’ve enjoyed discovering this recent API!

Here’s the code if you want to experiment with it:

import UIKit

// Before
let image = UIImage(named: "big-image")
imageView.image = image

// After
let image = UIImage(named: "big-image")

image?.prepareForDisplay { [weak self] preparedImage in
    DispatchQueue.main.async {
        self?.imageView.image = preparedImage
    }
}

// using Swift Concurrency
let image = UIImage(named: "big-image")

Task {
    imageView.image = await image?​.byPreparingForDisplay()
}
Previous
Previous

How to give great answers to technical interview questions 👩🏽‍💻👨🏻‍💻

Next
Next

Hidden feature: subscript