title | author | category | tags | excerpt | status | ||||
---|---|---|---|---|---|---|---|---|---|
NSCache |
Mattt |
Cocoa |
nshipster |
Poor NSCache, always being overshadowed by NSMutableDictionary. It's as if no one knew how it provides all of that garbage collection behavior that developers take great pains to re-implement themselves. |
|
Poor NSCache
, always being overshadowed by NSMutableDictionary
. It's as if no one knew how it provides all of that garbage collection behavior that developers take great pains to re-implement themselves.
That's right: NSCache
is basically just an NSMutableDictionary
that automatically evicts objects in order to free up space in memory as needed. No need to respond to memory warnings or contrive a cache-clearing timer mechanism. The only other difference is that keys aren't copied as they are in an NSMutableDictionary
, which is actually to its advantage (no need for keys to conform to <NSCopying>
).
If developers only knew...
But you're not like other devs, right? You won't overlook NSCache
, will you?
That's not to say that there aren't a few warts and inexplicable caveats—far from it. NSCache
is kind of a hot mess.
Take setObject:forKey:cost:
, for example. It's the same setObject:forKey:
method as before, but with this cost
parameter. What is that, you ask? Well, even the documentation isn't quite sure:
The cost value is used to compute a sum encompassing the costs of all the objects in the cache. When memory is limited or when the total cost of the cache eclipses the maximum allowed total cost, the cache could begin an eviction process to remove some of its elements.
Alright, so far so good...
However, this eviction process is not in a guaranteed order. As a consequence, if you try to manipulate the cost values to achieve some specific behavior, the consequences could be detrimental to your program.
Huh? So what's the point, then?
Typically, the obvious cost is the size of the value in bytes. If that information is not readily available, you should not go through the trouble of trying to compute it, as doing so will drive up the cost of using the cache.
So wait, what's a non-obvious cost value? Any guidelines for what a memory limit should be? How about an order of magnitude, even? "Arbitrarily guess wrong and suffer bad performance" doesn't sound so compelling...
Pass in 0 for the cost value if you otherwise have nothing useful to pass, or simply use the setObject:forKey: method, which does not require a cost value to be passed in.
Read: don't use this method unless you work at Apple and know the original author personally.
There's also a whole part about controlling whether objects are automatically evicted with evictsObjectsWithDiscardedContent
& <NSDiscardableContent>
, but it will probably just cause you more problems.
Despite all of this, developers should be using NSCache
a lot more than they currently are. Anything in your project that you call a "cache", but isn't NSCache
would be prime candidates for replacement. But if you do, just be sure to stick to the classics: objectForKey:
, setObject:forKey:
& removeObjectForKey:
.
Still not convinced? As a parting gift, we'll even make it easier, via a little subscripting majick:
extension NSCache {
subscript(key: AnyObject) -> AnyObject? {
get {
return objectForKey(key)
}
set {
if let value: AnyObject = newValue {
setObject(value, forKey: key)
} else {
removeObjectForKey(key)
}
}
}
}
Note: Due to changes in Objective-C generics in Swift 3, the subscript given above will only work in Swift 2.3 and earlier.