A fast image-loading pipeline

Networking Senior Swift 5.5+ iOS 16+ Reviewed By Alok Singh

Order the steps

Put the pipeline steps in order for one image request.

  • Look in the memory cache of decoded images
  • Look in the disk cache
  • Downsample and decode off the main thread
  • Store in the caches and deliver on the main actor if the cell still wants it
  • Join an in-flight request for the same URL, or start a download
Hint 1 of 1

Cheapest checks come first.

Choose an answer, then open Show answer below to check it.

Show answer

Answer: A B E C D

  1. Look in the memory cache of decoded images The fastest hit: no I/O or decoding.
  2. Look in the disk cache Avoids the network when the image was downloaded before.
  3. Join an in-flight request for the same URL, or start a download Coalescing prevents duplicate downloads.
  4. Downsample and decode off the main thread Decoding at display size saves memory and keeps scrolling smooth.
  5. Store in the caches and deliver on the main actor if the cell still wants it The identity check prevents wrong images in reused cells.

30-second answer

Check a memory cache of decoded images first, then a disk cache, and only then download. Coalesce requests for the same URL, downsample to the display size off the main thread, store results in both caches, and deliver on the main actor only if the cell still wants that image. Cancel requests for cells that scroll away and prefetch ones about to appear.

Practise saying it · 60 s. No recording, just a timer

Design an image pipeline.

Say your answer out loud, then check which of these points you covered:

  • Memory then disk cache
  • Coalesce downloads
  • Downsample off main
  • Cancel and prefetch

Deep dive

NSCache with a cost limit for decoded images; a size-bounded disk cache for data.

Downsample with ImageIO thumbnail options to avoid decoding full images.

Prioritise visible requests over prefetches.

Key caches by URL plus target size.

Coalescing loader Type-checked · Swift 6.4
actor ImageLoader {
    private var inFlight: [URL: Task<UIImage, Error>] = [:]
    private let cache = NSCache<NSURL, UIImage>()

    func image(for url: URL) async throws -> UIImage {
        if let cached = cache.object(forKey: url as NSURL) { return cached }
        if let task = inFlight[url] { return try await task.value }
        let task = Task {
            let (data, _) = try await URLSession.shared.data(from: url)
            guard let image = UIImage(data: data)?.preparingForDisplay() else { throw URLError(.cannotDecodeContentData) }
            return image
        }
        inFlight[url] = task
        defer { inFlight[url] = nil }
        let image = try await task.value
        cache.setObject(image, forKey: url as NSURL)
        return image
    }
}

Common mistakes

  • Decoding full-size images on the main thread.
  • Duplicate downloads for the same URL.

Senior take & follow-ups

Seniors walk through the pipeline end to end and name the measurements they watch.

Follow-up questions

Think of your answer first, then open the hint.

  1. Why key by size?

    Show hint

    A thumbnail and a full-screen image are different decoded bitmaps.

  2. How do you avoid duplicate downloads?

    Show hint

    An actor that maps URL to an in-flight Task.

Suggest a fix if something here is wrong or out of date.