I have been developing an application for iPad that is very graphically intensive. I have been able to squeeze quite a bit of performance out already on the iPad 2, but the @2x graphics for the new iPad are packing quite the punch in the memory department. Using the Activity Monitor in Instruments I am able to see the size of my application mushrooming into the 300MB-400MB range but I do not receive any low memory notifications. I am using a UINavigationController
to manage my views, so getting down into the stack has a cumulative effect on memory that ends in its eventual termination. I do not experience this problem on the iPad 2, where I receive low memory notifications as expected. My app has been coded to clean up as much as possible and performs very well on that device.
I have read a number of similar questions asked:
IOS app killed for Low Memory but no Memory Warning received
iPhone app uses 150 MB memory and still no low memory warning!
None of the suggestions seem to help.
I have inserted code to force a low-memory notification to be sent:
[[UIApplication sharedApplication] _performMemoryWarning];
This does cause the inactive views to unload as expected and returns memory consumption to normal. This uses a private API and is hack, so for practical reasons is not a solution. How do I get my device to properly respond to low memory conditions and let my app know that it needs to clean up??
This problem was fixed in iOS 5.1.1. For those users who are on 5.1, I have implemented my own memory watchdog and sending out a notification similar to the one used when a real memory warning is issued.
I first created a category on UIApplication
. This posts a notification that UIImage
listens for (or whatever its backing cache is) to unload cached images.
.h
@interface UIApplication (ForceLowMemory)
+ (void) forceUnload;
@end
.m
#import "UIApplication+ForceLowMemory.h"
@implementation UIApplication (ForceLowMemory)
+ (void)forceUnload {
[[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidReceiveMemoryWarningNotification
object:[UIApplication sharedApplication]];
}
@end
Next, I created a memory manager watchdog like the following:
.h
@interface ELMemoryManager : NSObject
- (void)startObserving;
- (void)stopObserving;
+ (ELMemoryManager*) sharedInstance;
@end
.m
#import "ELMemoryManager.h"
#import "UIApplication+ForceLowMemory.h"
#import <mach/mach.h>
@interface ELMemoryManager()
@property (nonatomic, retain) NSTimer *timer;
uint report_memory(void);
@end
#define MAX_MEM_SIZE 475000000
@implementation ELMemoryManager
@synthesize timer = timer_;
static ELMemoryManager* manager;
#pragma mark - Singleton
+ (void) initialize {
if (manager == nil) {
manager = [[ELMemoryManager alloc] init];
}
}
+ (ELMemoryManager*) sharedInstance {
return manager;
}
#pragma mark - Instance Methods
uint report_memory(void) {
struct task_basic_info info;
mach_msg_type_number_t size = sizeof(info);
kern_return_t kerr = task_info(mach_task_self(),
TASK_BASIC_INFO,
(task_info_t)&info,
&size);
if( kerr == KERN_SUCCESS ) {
return info.resident_size;
} else {
return 0;
}
}
- (void)startObserving {
if (!self.timer) {
NSTimer* timer = [NSTimer scheduledTimerWithTimeInterval:5.0f target:self selector:@selector(checkMemory:) userInfo:nil repeats:YES];
self.timer = timer;
}
[self.timer fire];
}
- (void)stopObserving {
[self.timer invalidate];
self.timer = nil;
}
- (void)checkMemory:(id)sender {
uint size = report_memory();
if (size > MAX_MEM_SIZE) {
NSLog(@"we've busted the upper limit!!!");
[UIApplication forceUnload];
}
}
#pragma mark - Memory Management
- (void)dealloc {
[self.timer invalidate];
[timer_ release];
[super dealloc];
}
@end