Caching in Swift (iOS/macOS): from in-memory to Redis
In iOS and macOS development, caching is a key tool for speeding up applications, reducing server load, and improving user experience. In this article, we will look at the main approaches to caching in Swift: from simple NSCache to professional solutions based on Redis and SQLite. Each method has its own area of application, and the right choice depends on the data type, its volume, and persistence requirements.
1. NSCache — built-in in-memory caching
NSCache is a lightweight, thread-safe cache built into Foundation. It automatically frees memory when resources are low, making it ideal for caching images, API data, and other temporary objects.
import Foundation
class ImageCache { private let cache = NSCache<NSString, UIImage>() func getImage(forKey key: String) -> UIImage? { return cache.object(forKey: key as NSString) } func setImage(_ image: UIImage, forKey key: String) { cache.setObject(image, forKey: key as NSString) } func removeImage(forKey key: String) { cache.removeObject(forKey: key as NSString) }}Advantages:
- No setup required — works out of the box.
- Automatically manages memory (removes objects on
didReceiveMemoryWarning). - Thread-safe.
Disadvantages:
- Data is lost when the application restarts.
- Not suitable for large volumes of data (limited by RAM).
2. Core Data as a persistent cache
Core Data is often used not only as an ORM but also as a cache for structured data. This is convenient when you need to save complex objects with relationships and perform filtering.
import CoreData
class CoreDataCache { private let context: NSManagedObjectContext init(context: NSManagedObjectContext) { self.context = context } func cacheResponse(_ data: Data, forKey key: String) { let cached = CachedResponse(context: context) cached.key = key cached.data = data cached.timestamp = Date() try? context.save() } func getCachedResponse(forKey key: String) -> Data? { let fetchRequest: NSFetchRequest<CachedResponse> = CachedResponse.fetchRequest() fetchRequest.predicate = NSPredicate(format: "key == %@", key) fetchRequest.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: false)] fetchRequest.fetchLimit = 1 guard let result = try? context.fetch(fetchRequest).first else { return nil } return result.data }}When to use:
- Data must persist between sessions.
- Complex filtering is needed (e.g., text search).
- Data volume is moderate (up to several hundred thousand records).
3. Redis — server-side caching for Swift
Redis is a high-performance in-memory data store that works over the network. In the context of Swift (iOS/macOS), Redis is most often used on the server side (Vapor, Kitura) for caching sessions, results of complex queries, and task queues. However, there are client libraries that allow connecting to Redis directly from a mobile application (e.g., via RediStack).
import RediStack
final class RedisCache { private let connection: RedisConnection init(host: String, port: Int = 6379) async throws { self.connection = try await RedisConnection.connect( to: .init(hostname: host, port: port) ) }