Search code examples
iosswiftcaching

NSCache doesn't delete items when under memory pressure when I am testing it, however the documentation says it does


This is what I see in the documentation.

The NSCache class incorporates various auto-eviction policies, which ensure that a cache doesn’t use too much of the system’s memory. If memory is needed by other applications, these policies remove some items from the cache, minimizing its memory footprint.

But when I look into the source code: https://github.com/apple/swift-corelibs-foundation/blob/main/Sources/Foundation/NSCache.swift

I see nothing about it somehow deletes items, when under memory pressure. It only deletes items when you reach the cost limit.

I made a small test:

class Data {
    var data = [Int]()

    init() {
        for i in 0..<1000000 {
            data.append(i)
        }
    }
}

var cache = NSCache<NSNumber, Data>()

for i in 0..<10000000 {
    cache.setObject(Data(), forKey: NSNumber(value: i))
}

And after that test the app eats all the memory and crashes. So. Does the documentation lie?


Solution

  • First, and most importantly, swift-corelibs-foundation is not the Foundation from iOS and macOS:

    This project, swift-corelibs-foundation, provides an implementation of the Foundation API for platforms where there is no Objective-C runtime. On macOS, iOS, and other Apple platforms, apps should use the Foundation that comes with the operating system. Our goal is for the API in this project to match the OS-provided Foundation and abstract away the exact underlying platform as much as possible.

    Next, your test case does not carefully test NSCache. Your Data objects could be stored on the autorelease pool and not released. In this case that's not true, but it's the kind of thing you need to be very careful about when testing memory usage.

    That said, I can't reproduce your issue. When I put this in an iOS app and run it on iOS 12, I see the kind of behavior you're probably expecting:

    enter image description here

    Big run-up in memory usage to ~1GB, at which point the cache is dumped as expected.

    I also wasn't able to easily reproduce it on macOS (but I wasn't patient enough to let it crash, so it's possible it eventually would). You'll need to give a more precise example of where this crashes unexpectedly in order to diagnose why.