The New iPad: Low Memory Warnings Not Appearing?
Asked Answered
W

6

21

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??

Whaley answered 17/3, 2012 at 21:2 Comment(4)
Have you tested with iOS 5.1 on the iPad 2 as well?Soave
Can you confirm that with both the iPads and same iOS 5.1, when you load exactly the same sequence of images (\@1x for iPad2 and \@2x for iPad3) and view controllers the app terminates on iPad3 and doesn't terminate on iPad2? and another test, if you get rid of the @2x images, will you receive on iPad3 (once more images are loaded of course) memory warnings or the app will still terminate?Ked
I can confirm that the same exact sequence of events will work fine on iPad 2 (Running 5.1), but cause a crash on iPad 3. I have to be quite abusive on the iPad 2 to force it into a low memory situation.Whaley
When I remove all the @2x images my app runs with the same footprint as the iPad 2 and performs just fine.Whaley
W
2

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
Whaley answered 7/5, 2012 at 22:28 Comment(1)
If I put a breakpoint in my AppDelegate's receiveMemoryWarning: (or whatever the signature was), it won't get called if I send the notification you use. Same for you?Northrop
T
12

I contacted the Apple Support to solve my memory problem and asked about the low-memory warnings on iPad 3:

-Because memory warnings are delivered on the main thread, your app will not receive memory warnings if it's blocking the main thread.

-Even if your app is not blocking the main thread, it is possible for memory usage to grow quickly enough that memory warnings are not delivered before your app is killed to free memory.

-Memory warnings trigger when the kernel transitions between various levels of memory pressure. Because of this, it is common for an app to receive a memory warning and then be killed quite some time later when memory is exhausted. The initial memory warning freed enough memory to keep the app alive, but not enough for the kernel to transition to a lower level of memory pressure.

Because of all this, memory warning should be treated as a useful data on the state of the hardware and a good guide on how much memory your app should use on a given device, but should not be relied on as a tool to prevent your app from being killed.

Maybe this helps...

Tamarra answered 4/4, 2012 at 10:17 Comment(2)
I'm not getting any memory warnings at all. I have reduced my main thread blocking to an absolute minimum. I think the problem here is that there is a pretty narrow margin between how much memory my app is using and when a memory condition occurs and loading my heavy xibs exceeds that gap and is terminated without warning.Whaley
I created a test app, that just messes up the memory by running a while loop and allocating an NSData object. I get warnings on iPad 2 but not on iPad 3 with the very same app.Tamarra
W
2

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
Whaley answered 7/5, 2012 at 22:28 Comment(1)
If I put a breakpoint in my AppDelegate's receiveMemoryWarning: (or whatever the signature was), it won't get called if I send the notification you use. Same for you?Northrop
J
1

The below is my experience. I am quite happy to be corrected...

How are you loading your images?

If you are using:

[UIImage imageNamed:(NSString *)]

Then you need to make sure that there is a good reason to. If you are making heavy use of an image that needs to be cached, then its a good option. Otherwise I would suggest you use

[UIIMage imageWithContentsOfFile:(NSString *)]

iOS appears to have some issues releasing images loaded via imageNamed, even if there are no longer any references to it. Since your app will no longer have any references to the image, you probably wont get the memory warnings. But that doesn't mean that the memory has been freed. iOS tends to keep these images in memory much longer than you would like. When it would normally give a memory warning, it will just terminate the app.

I would also highly recommend turning on Automatic Reference Counting (ARC).

Edit -> Refactor -> Convert to Objective-C ARC...

I had similar issues for quite a while. Making the above changes to my app stopped the crashes, and stopped the memory leak caused when I reloaded the same image over and over again via imageNamed.

Hope that helps.

Jerejereld answered 23/3, 2012 at 5:59 Comment(5)
There was leak in [UIImage imageNamed:] method in the ios 2.x but it was repaired in ios 3.0Extramarital
Images are loaded through IB.Whaley
Maybe it could be the problem.Extramarital
@MartinPilch - The issue I was having with [UIImage imageNamed:] was occurring in 5.0 and 5.1 - And it was when I was loading and removing large (full screen) images over and over (in a carousel). I would only ever have the current previous and next images loaded. Once they were off the screen I would unload them (Swap them for a placeholder image). When they returned to any of the three positions I would load them again. Doing this multiple times with [UIImage imageNamed:] would lead to available memory being reduced to nothing and the app crashing (without a memory warning)Jerejereld
@user499177 I had the same issue. It is because autorelease object is basically leak. I would suggest you to use NSAutoreleasePool and drain it when zou have finished work with the image.Extramarital
S
1

It sounds to me like the issue is that your images are not being released. According to the UIImage documentation (and my experience), imageNamed: caches the images it loads. Therefore you should use it for small icons or images that are almost constantly used, but it's generally a bad idea to use it for large images or images that are used infrequently. As user499177 says, you should use imageWithContentsOfFile: (you can use [NSBundle mainBundle] methods to derive the path) instead:

This method looks in the system caches for an image object with the specified name and returns that object if it exists. If a matching image object is not already in the cache, this method loads the image data from the specified file, caches it, and then returns the resulting object.

Snowden answered 3/4, 2012 at 10:34 Comment(2)
99.99% of my images are being loaded through XIB.Whaley
I'm pretty sure the XIB loading uses the cache. If you have lots of views all with (e.g.) different backgrounds then this will quickly eat your available RAM.Snowden
E
0

I have been working on the app consuming lot of the memory and my suggestions are:

  1. Use alloc - init and release, avoid using autorelease objects.
  2. Check for memory leaks
  3. If you need to use autorelease object, use NSAutoreleasePool before you create the object and drain the pool when you have finished the work with the object.
  4. If you are using OpenGL remember to delete all the created objects, especially textures
  5. Maybe you should try to switch to ARC
Extramarital answered 23/3, 2012 at 9:22 Comment(2)
I mostly use alloc/init, have 0 leaks, and those instances where warranted, do use an autorelease pool.Whaley
Just a side note to say that I'm using ARC with a project that employs large 2X bitmaps all over the place and I have yet to see a low mem error (or crash) on the 3rd gen iPad. I'm very impressed with ARC at this point, after a rocky start.Laudation
R
0

Mega-Dittos. This has been very reassuring. I too am doing an image intensive app (lots of UIImage objects in UIImageView for animation) and have the usual memory warning code in my app delegate but it is never triggered. Nonetheless, Instruments showed the problem as I loaded images, drew on them and committed them to the imageviews. I'm using ARC, got rid of leaks from CGImageRefs and the like but at the end of the day if you load up enough images in an ImageView, quickly enough you will get a crash without warning of any kind in the log, app delegate method callback, or instruments. The app just gets the rug pulled out from under it without so much as a "by your leave."

Haven't had a chance to try this on an iPad2 yet but regardless, there needs to be SOME indication, at least a minimalist console message or something. Most of the loading happens on my own GCD queue, not the main thread though by definition updates to screen controls have to be done on the main thread. So I supupose if that's blocking when the run is about to be pulled then I guess you can just get an anonymous failure. Sure would help for this to get SOME kind of console message though.

Rabbit answered 2/5, 2012 at 16:5 Comment(1)
I have resorted to creating my own memory manager and manually posting the notification that causes inactive views to unload and UIImage to flush the image cache. I still get sporadic crashes when, like you, I try to load too many at once on a background thread. There may be some Apple watchdog process that guards against sudden spikes of memory, even though you haven't technically reached the maximum memory threshold.Whaley

© 2022 - 2024 — McMap. All rights reserved.