What's the best approach to asynchronous image caching on the iPhone?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Asynchronous image caching is really a coordination problem between networking, decoding, memory usage, and cell reuse. The best approach is usually a layered one: use URLSession for background fetch, an in-memory cache for fast reuse, a disk cache for persistence, and careful UI updates so reused cells do not display the wrong image. Most bugs in this area come from lifecycle mistakes rather than from the cache container itself.
Use Memory Cache for Fast Reuse
NSCache is the natural in-memory cache on iOS because it can evict items automatically under memory pressure.
This should be the first place you look before starting a new network request. Memory cache is much faster than disk or network access, so it is what keeps scrolling smooth when images are revisited.
Download Asynchronously with URLSession
The network part should happen off the main thread, with UI updates marshaled back to the main queue.
This is the minimum viable pattern: cache hit first, async fetch second, UI update on the main queue last.
Account for Cell Reuse
Table and collection view cells are reused. If an old request finishes after the cell has been reassigned to different data, the wrong image can appear in the wrong row.
That is why image loading code should either cancel stale requests, compare the current expected URL before assigning the image, or use a dedicated image-loading abstraction that handles reuse safely.
The cache is only half the system. Correct binding between model and cell matters just as much.
Disk Cache Helps Across Launches
Memory cache disappears when the app terminates or when the system purges it. If images are expensive to re-download, a disk cache layer is useful.
In production, this is often handled by an image library or by a small custom cache that stores image data under a hashed file name. The important design idea is to check memory first, disk second, and network last.
That ordering keeps the interface responsive while still giving persistence across launches.
Avoid Heavy Work on the Main Thread
Downloading is not the only expensive part. Image decoding and resizing can also hurt scrolling performance if done at the wrong time.
If the app shows many large remote images, pre-sizing or decoding them off the main thread can be just as important as caching them. Otherwise the network layer looks innocent while the UI still stutters.
Libraries Are Often Worth It
For many production apps, the best approach is not writing the entire pipeline by hand. Established libraries exist because request deduplication, cancellation, resizing, memory pressure, and disk persistence are harder than they first appear.
Even if you use a library, though, the architecture is still the same: async fetch, cache hierarchy, and safe UI binding.
Common Pitfalls
- Starting duplicate downloads because no cache lookup happens before the request.
- Updating a reused cell with a stale image result.
- Treating memory cache as if it were persistent storage.
- Doing image decoding or resizing work on the main thread.
- Assuming the cache alone fixes performance without considering cell lifecycle and cancellation.
Summary
- Use a layered design with async networking, memory cache, and often disk cache.
- '
NSCacheis a good in-memory starting point on iOS.' - Always account for cell reuse and stale completion handlers.
- Keep network and image processing work off the main thread.
- For production apps, a mature image-loading library is often the pragmatic choice.

