SwiftUI does not currently provide a built-in caching mechanism, even though caching is often essential for scenarios such as:
- Storing downloaded images, so they do not need to be fetched repeatedly.
- Storing expensive computed data—like attributed text—that involves complex rendering or transformations.
While we might consider other solutions, NSCache is a robust option. However, NSCache stipulates that both its keys and values must be objects (AnyObject
). This poses a barrier in SwiftUI projects where data is frequently represented by structs, which are not classes and thus not directly compatible with AnyObject
. Fortunately, Swift types can conform to Hashable
, allowing you to translate them into a form suitable for NSCache.
To make a hashable struct usable as an NSCache key, you can convert it to an NSNumber
based on its hash value. Below is a simple extension to AnyHashable
:
extension AnyHashable {
var nsCacheKey: NSNumber {
// Convert the hashValue of the underlying type into an NSNumber.
NSNumber(integerLiteral: hashValue)
}
}
Because AnyHashable
can wrap any hashable value, this extension makes it easy to generate an object key for NSCache. The type NSNumber
neatly accommodates the integer-based hashValue
, though you could pick another object-based solution if you prefer.
Example: Caching Downloaded Images
Here is a more elaborate demonstration of using NSCache with a hashable struct (in this case, several related properties wrapped in your own type). This struct will conform to Hashable
.
// 1. Define a compound hashable struct representing the image request with multiple attributes.
struct CompoundImageRequest: Hashable {
let url: URL
let identifier: String
let version: Int
}
This CompoundImageRequest
includes a URL, an identifier, and a version number. By conforming to Hashable
, Swift will synthesise a hash function that combines these properties, making the struct uniquely identifiable based on its contents.
Updated Cache and View Using the Compound Key
We now adjust our caching logic and SwiftUI view to utilise CompoundImageRequest
as the cache key.
// 2. A wrapper class around NSCache, storing images using AnyHashable keys.
final class ImageCache {
static let shared = ImageCache()
private let cache = NSCache<NSNumber, UIImage>()
private init() {}
// Retrieve the image from the cache using a compound key
func image(for request: AnyHashable) -> UIImage? {
cache.object(forKey: request.nsCacheKey)
}
// Save the image to the cache using a compound key
func insertImage(_ image: UIImage, for request: AnyHashable) {
cache.setObject(image, forKey: request.nsCacheKey)
}
// Add other functions such as deletetion as needed.
}
// 3. A SwiftUI view that fetches and caches images with a compound key.
struct CompoundCachingImageView: View {
let request: CompoundImageRequest
@State private var image: UIImage?
var body: some View {
Group {
if let uiImage = image {
Image(uiImage: uiImage)
.resizable()
.scaledToFit()
} else {
ProgressView("Loading…")
}
}
.onAppear {
// Check if the image is already cached with the compound key
if let cachedImage = ImageCache.shared.image(for: request) {
image = cachedImage
return
}
// Otherwise, download the image and store it in the cache
Task {
do {
let (data, _) = try await URLSession.shared.data(from: request.url)
if let uiImage = UIImage(data: data) {
ImageCache.shared.insertImage(uiImage, for: request)
image = uiImage
}
} catch {
// Handle download error appropriately
}
}
}
}
}
In this example:
CompoundImageRequest
is a hashable struct containing multiple properties (URL, identifier, version). ItsHashable
conformance allows it to be uniquely represented by a hash value derived from all its components.- Caching Mechanism: The
CompoundImageCache
class utilisesNSCache<NSNumber, UIImage>
and converts the compound struct into anNSNumber
key using the extension onAnyHashable
. This approach preserves NSCache’s requirements while allowing the use of a multi-attribute key. CompoundCachingImageView
checks the cache before making a network request, demonstrating a common SwiftUI pattern: load if not cached, and display the image when available.
This pattern can be adapted for many other caching needs in SwiftUI—whether you are caching data, text attributes, or other computed resources. By wrapping your hashable structs with AnyHashable
and utilising the .nsCacheKey
computed property, you can transform conventional Swift structs into NSCache-compatible keys with minimal extra code while keeping the mapping logic simple.