NSCache crashing when memory limit is reached (only on iOS 7)
Asked Answered
T

2

9

We are using NSCache for UIImages in our app. This works fine on iOS versions smaller than 7. When a memory warning occurs, NSCache releases objects as intended. However, on iOS 7, our app crashes shortly after the first memory warning. So it seems as if objects stored with NSCache are never released but the cache is growing until the app is crashing. Profiling with instruments confirms this suspicion.

Did somebody else experience this problem and did you find a workaround or already track a bug?

It looks like those guys had the same issue: http://www.photosmithapp.com/index.php/2013/10/photosmith-3-0-2-photo-caching-and-ios-7/

I created a small sample app to illustrate the issue. When a button is pressed, the method -(IBAction)fillCache:(id)sender is called. From then on, a timer calls -(void)addImageToCache:(id)sender every 100 ms. In this method, a UIImage is generated and written to cache.

On the iPad Mini with iOS 7.0.3 and its 512 MB memory, it crashes after ~350 iterations.

On the iPad 2 with iOS 5 and also 512 MB memory, it also crashes at some point, but only after at least 3000 iterations. Instruments shows that the number of UIImage instances decreases everytime a memory warning occurs. This is not the case on iOS 7.

- (IBAction)fillCache:(id)sender
{
    [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(addImageToCache:) userInfo:nil repeats:YES];
}

- (void)addImageToCache:(id)sender
{
    @autoreleasepool {

        CGRect rect = CGRectMake(0, 0, 500, 500);
        UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0);
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();

        NSString *poolKey = [NSString stringWithFormat:@"junk_%d", count++];
        [self.cache setObject:image forKey:poolKey];

    }
}
Thyme answered 23/10, 2013 at 15:32 Comment(0)
I
18

While NSCache never responded to memory warnings, I found that it generally responded to true memory pressure. The failure to respond to memory warnings has always been a bit of an annoyance (e.g. you couldn't just use the "simulate memory warning" to test the behavior of an app in memory pressure).

Having said that, I see the same behavior you describe. iOS 7 seems to have changed the NSCache behavior.

Personally, I just have simple-minded NSCache subclass that just removes all of the objects upon receiving the UIApplicationDidReceiveMemoryWarningNotification notification:

@implementation AutoPurgeCache

- (id)init
{
    self = [super init];
    if (self) {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
    }
    return self;
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}

@end
Intracranial answered 23/10, 2013 at 18:2 Comment(4)
this seems sane and fine... I wonder if it is a little heavy handed, could you... reduce the totalCostLimit or the countLimit instead?Twopence
@GradyPlayer Agreed. I like your idea, but just wasn't sure how you could be confident that changing the limits as you've suggested would be enough to restore the app to a stable situation. One could also set reasonable limits up-front and might prevent the memory alert from occurring in the first place. But if you do that and still get memory warnings, then this heavy-handed approach might be a bulwark against catastrophic failure.Intracranial
you are probably right, I don't know if it takes a spin or two through the runloop either... I guess you shouldn't have anything in there that you can't recreate later.Twopence
@Intracranial Thanks, just implemented your solution, seems to be a good and clean idea. Also seems to work as intended: so far no crashes, everythings works fine. (I'd just add a call to [super dealloc] in the dealloc method - at least when not using ARC)Thyme
M
3

The NSCache object removes its data basing on its own rules. That doesn't mean that it will release content during a memory warning.
Here what the doc states:

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.

Most probably the changed some policies in iOS7. You can remove all contents by listening to memory warning notification. I link this answer for sake of completeness.

Morula answered 23/10, 2013 at 15:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.