A fast, simple, and lightweight image caching library for iOS, macOS, tvOS, and watchOS.
JMImageCache provides both in-memory (NSCache) and disk-based caching for images downloaded from the network. It supports modern Swift async/await patterns while maintaining full backward compatibility with the original Objective-C API.
- In-memory caching - Automatic memory management via NSCache
- Disk caching - Persistent storage with SHA1-hashed filenames
- Async/await support - Modern Swift concurrency patterns
- SwiftUI support -
CachedAsyncImageview component - UIKit support -
UIImageViewextension for easy image loading - Backward compatible - Original Objective-C API still works
- Cross-platform - iOS, macOS, tvOS, and watchOS
- Thread-safe - All operations are properly synchronized
- Lightweight - No external dependencies
- iOS 15.0+ / macOS 12.0+ / tvOS 15.0+ / watchOS 8.0+
- Swift 5.9+
- Xcode 15.0+
For older iOS versions (5.0-14.x), use the legacy Objective-C implementation included in this repo.
Add JMImageCache to your project via SPM:
dependencies:[.package(url:"https://github.com/jakemarsh/JMImageCache.git", from:"2.0.0")]Or in Xcode: File → Add Package Dependencies → Enter the repository URL.
pod'JMImageCache'import JMImageCache
// Simple async/await
letimage=tryawaitJMImageCache.shared.image(for: url)
// With custom cache key
letimage=tryawaitJMImageCache.shared.image(for: url, key:"my-custom-key")JMImageCache.shared.image(for: url){ image in
imageView.image = image
} failure:{ error inprint("Failed: \(error)")}import JMImageCache
structContentView:View{varbody:someView{CachedAsyncImage(url:URL(string:"https://example.com/image.jpg")){ phase inswitch phase {case.empty:ProgressView()case.success(let image):
image
.resizable().aspectRatio(contentMode:.fit)case.failure:Image(systemName:"photo").foregroundColor(.gray)}}}}import JMImageCache
// Simple usage
imageView.setImage(with: url)
// With placeholder
imageView.setImage(with: url, placeholder:UIImage(named:"placeholder"))
// With callbacks
imageView.setImage(with: url, placeholder: placeholderImage){ image inprint("Loaded!")} failure:{ error inprint("Error: \(error)")}
// Cancel loading
imageView.cancelImageLoad()#import"JMImageCache.h"// UIImageView category
[imageView setImageWithURL:url placeholder:placeholderImage];
// Direct cache access
[[JMImageCache sharedCache] imageForURL:url completionBlock:^(UIImage *image) {
self.imageView.image = image;
}];Images can be in three states:
- Cached In Memory - Returned immediately
- Cached On Disk - Loaded from disk, moved to memory, returned
- Not Cached - Downloaded, saved to disk, cached in memory, returned
This approach ensures the fastest possible image delivery while minimizing network requests.
// Swift
JMImageCache.shared.removeAllImages()
// Objective-C
[[JMImageCache sharedCache] removeAllObjects];// Swift
JMImageCache.shared.removeImage(for: url)JMImageCache.shared.removeImage(forKey:"my-key")
// Objective-C
[[JMImageCache sharedCache] removeImageForURL:url];// Swift
letimage=tryawaitJMImageCache.shared.image(for: url)
// Image is now cached for future use// Swift
iflet cached =JMImageCache.shared.cachedImage(for: url){
// Image is in memory cache
}// Custom cache directory
letcache=JMImageCache(
cacheDirectory: customURL,
urlSession:.shared
)The Swift rewrite maintains API compatibility. Most Objective-C code will continue to work. For Swift projects:
| Old API (Obj-C) | New API (Swift) |
|---|---|
[imageView setImageWithURL:url placeholder:placeholder] | imageView.setImage(with: url, placeholder: placeholder) |
[[JMImageCache sharedCache] imageForURL:url completionBlock:^...] | try await JMImageCache.shared.image(for: url) |
[[JMImageCache sharedCache] cachedImageForURL:url] | JMImageCache.shared.cachedImage(for: url) |
[[JMImageCache sharedCache] removeAllObjects] | JMImageCache.shared.removeAllImages() |
do{letimage=tryawaitJMImageCache.shared.image(for: url)}catchJMImageCacheError.invalidResponse(let response){
// Server returned an error
}catchJMImageCacheError.invalidImageData {
// Data couldn't be converted to an image
}catch{
// Other error
}All JMImageCache operations are thread-safe:
- Memory cache operations are synchronized via NSCache
- Disk operations run on a dedicated serial queue
- Completion handlers and async results are always delivered on the main thread
The repository includes a demo project showing typical usage in a UITableViewController with image loading.
JMImageCache is available under the MIT license. See the LICENSE file for details.
Jake Marsh (@jakemarsh)
Originally created in 2011, rewritten in Swift with async/await in 2024.