NSCache
Cache Library
NSCache
Overview
NSCache is a memory caching class for Apple platforms (iOS, macOS, watchOS, tvOS) that automatically removes entries when the system is low on memory, optimizing app memory efficiency. Available in both Swift and Objective-C, it provides fast access to temporary data such as UIImage and custom objects.
Details
NSCache is a "reactive cache" provided as part of the Foundation framework. It aggressively caches data during normal operation but automatically removes some entries to free memory when the system experiences memory pressure. This reduces the risk of the app being terminated due to memory shortage.
Key technical features:
- Automatic eviction: Automatic removal during system memory shortage
- Thread-safe: Safe access from multiple threads
- Limit settings: Upper limits for entry count and total cost
- Objective-C foundation: NSObject-based key-value pairs
Technical constraints:
- Class types only: Cannot directly store structs
- NSString keys: Keys must be NSObject-based
- Unpredictable: Cannot control eviction timing
Pros and Cons
Pros
- Automatic memory management: Auto-removal during system pressure
- Thread-safe: No synchronization required
- High performance: Fast access through native implementation
- Platform integration: Support for all Apple platforms
- Easy adoption: Standard library with no additional dependencies
- Memory efficient: Supports long-running applications
- Official support: Maintained and supported by Apple
Cons
- Apple-only: Limited to iOS/macOS/watchOS/tvOS
- Type constraints: Only supports class types and NSObject keys
- Unpredictable: Cannot control eviction behavior
- Swift constraints: Cannot directly use String keys (requires NSString)
- No persistence: Data lost when app terminates
- No distribution: Single-process caching only
- Limited features: No TTL or custom eviction policies
Reference Links
- Official Documentation - Apple Developer
- NSHipster - NSCache
- Hacking with Swift - NSCache tutorial
- Swift by Sundell - Caching in Swift
Usage Examples
Basic Cache Usage
import Foundation
let cache = NSCache<NSString, UIImage>()
cache.name = "ImageCache"
// Store image in cache
let image = UIImage(named: "sample")
cache.setObject(image!, forKey: "sample_image")
// Retrieve image from cache
if let cachedImage = cache.object(forKey: "sample_image") {
print("Retrieved image from cache")
}
// Remove specific object from cache
cache.removeObject(forKey: "sample_image")
Limit Settings and Cost Management
import Foundation
let cache = NSCache<NSString, AnyObject>()
// Set entry count limit (e.g., up to 5 entries)
cache.countLimit = 5
// Set total cost limit (e.g., 10MB)
cache.totalCostLimit = 10 * 1024 * 1024
// Store object with cost
let largeData = Data(count: 1024 * 1024) // 1MB data
cache.setObject(largeData as AnyObject,
forKey: "large_data",
cost: largeData.count)
print("Current cache settings:")
print("- Entry count limit: \(cache.countLimit)")
print("- Total cost limit: \(cache.totalCostLimit)")
Remote Image Cache Implementation
import Foundation
import UIKit
class ImageCacheManager {
private let cache = NSCache<NSString, UIImage>()
init() {
cache.name = "RemoteImageCache"
cache.countLimit = 50 // Max 50 images
cache.totalCostLimit = 50 * 1024 * 1024 // 50MB
}
func loadImage(from urlString: String, completion: @escaping (UIImage?) -> Void) {
let key = urlString as NSString
// Check cache first
if let cachedImage = cache.object(forKey: key) {
completion(cachedImage)
return
}
// Fetch from network
guard let url = URL(string: urlString) else {
completion(nil)
return
}
URLSession.shared.dataTask(with: url) { [weak self] data, _, error in
guard let data = data, let image = UIImage(data: data) else {
completion(nil)
return
}
// Store in cache (cost is image data size)
self?.cache.setObject(image, forKey: key, cost: data.count)
DispatchQueue.main.async {
completion(image)
}
}.resume()
}
}
// Usage example
let imageManager = ImageCacheManager()
imageManager.loadImage(from: "https://example.com/image.jpg") { image in
if let image = image {
// Display image
print("Image loaded successfully")
}
}
Custom Object Caching
import Foundation
// Custom class for caching
class UserProfile: NSObject {
let id: String
let name: String
let avatar: Data?
init(id: String, name: String, avatar: Data? = nil) {
self.id = id
self.name = name
self.avatar = avatar
}
}
class UserProfileCache {
private let cache = NSCache<NSString, UserProfile>()
init() {
cache.name = "UserProfileCache"
cache.countLimit = 100
// Register observer for memory warnings
NotificationCenter.default.addObserver(
self,
selector: #selector(clearCache),
name: UIApplication.didReceiveMemoryWarningNotification,
object: nil
)
}
func setProfile(_ profile: UserProfile) {
let key = profile.id as NSString
let cost = profile.avatar?.count ?? 0
cache.setObject(profile, forKey: key, cost: cost)
}
func getProfile(for userId: String) -> UserProfile? {
return cache.object(forKey: userId as NSString)
}
@objc private func clearCache() {
cache.removeAllObjects()
print("Cache cleared due to memory warning")
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
// Usage example
let userCache = UserProfileCache()
let profile = UserProfile(id: "user123", name: "John Doe")
userCache.setProfile(profile)
if let cachedProfile = userCache.getProfile(for: "user123") {
print("User: \(cachedProfile.name)")
}
Objective-C Usage
#import <Foundation/Foundation.h>
@interface ImageCache : NSObject
@property (nonatomic, strong) NSCache *cache;
@end
@implementation ImageCache
- (instancetype)init {
if (self = [super init]) {
_cache = [[NSCache alloc] init];
_cache.name = @"ImageCache";
_cache.countLimit = 20;
_cache.totalCostLimit = 20 * 1024 * 1024; // 20MB
}
return self;
}
- (void)cacheImage:(UIImage *)image forKey:(NSString *)key {
if (image && key) {
NSData *imageData = UIImagePNGRepresentation(image);
[self.cache setObject:image forKey:key cost:imageData.length];
}
}
- (UIImage *)imageForKey:(NSString *)key {
return [self.cache objectForKey:key];
}
- (void)removeImageForKey:(NSString *)key {
[self.cache removeObjectForKey:key];
}
@end
Thread-safe Access Pattern
import Foundation
class ThreadSafeCache {
private let cache = NSCache<NSString, AnyObject>()
private let queue = DispatchQueue(label: "cache.queue", attributes: .concurrent)
func setObject(_ object: AnyObject, forKey key: String) {
queue.async(flags: .barrier) {
self.cache.setObject(object, forKey: key as NSString)
}
}
func object(forKey key: String, completion: @escaping (AnyObject?) -> Void) {
queue.async {
let object = self.cache.object(forKey: key as NSString)
DispatchQueue.main.async {
completion(object)
}
}
}
func removeObject(forKey key: String) {
queue.async(flags: .barrier) {
self.cache.removeObject(forKey: key as NSString)
}
}
}
// Usage example
let threadSafeCache = ThreadSafeCache()
// Safe access from multiple threads
DispatchQueue.global().async {
threadSafeCache.setObject("Data 1" as AnyObject, forKey: "key1")
}
threadSafeCache.object(forKey: "key1") { object in
if let data = object as? String {
print("Retrieved: \(data)")
}
}