I have come across an issue when loading different NSImages repeatedly in an application written using Automatic Reference Counting. It seems as though ARC is not releasing the image objects correctly, and instead the memory usage increases as the list is iterated until the iteration is complete, at which point the memory is freed.
I am seeing up to 2GB of memory being used through the process in some cases. There's a good discussion on SO on a very similar issue which ends up putting the process in an NSAutoReleasePool and releasing the image and draining the pool. This works for me as well if I don't use ARC, but it is not possible to make calls to these objects / methods in ARC.
Is there any way to make this work in ARC as well? It seems as though ARC should be figuring this out all by itself - which makes me think that the fact that these objects are not getting released must be a bug is OS X. (I'm running Lion with XCode 4.2.1).
The kind of code which is causing the issue looks like this:
+(BOOL)checkImage:(NSURL *)imageURL
{
NSImage *img = [[NSImage alloc] initWithContentsOfURL:imageURL];
if (!img)
return NO;
// Do some processing
return YES;
}
This method is called repeatedly in a loop (for example, 300 times). Profiling the app, the memory usage continues to increase with 7.5MB alloced for each image. If ARC is not used, the following can be done (as suggested in this topic):
+(BOOL)checkImage:(NSURL *)imageURL
{
NSAutoReleasePool *apool = [[NSAutoReleasePool alloc] init];
NSImage *img = [[NSImage alloc] initWithContentsOfURL:imageURL];
if (!img)
return NO;
// Do some processing
[img release];
[apool drain];
return YES;
}
Does anyone know how to force ARC to do the memory cleaning? For the time being, I have put the function in a file which is compiled with -fno-objc-arc passed in as a compiler flag. This works OK, but it would be nice to have ARC do it for me.
use #autoreleasepool, like so:
+(BOOL)checkImage:(NSURL *)imageURL
{
#autoreleasepool { // << push a new pool on the autotrelease pool stack
NSImage *img = [[NSImage alloc] initWithContentsOfURL:imageURL];
if (!img) {
return NO;
}
// Do some processing
} // << pushed pool popped at scope exit
return YES;
}
Related
I'm using the profiler in xcode 4 to determinate if I have any memory leaks. I didn't have this leak before, but with xcode 5 I have this one.
I'm trying to set an image for the tab item of my `UIViewController and the profiler marks this line :
image = [[UIImage alloc] initWithContentsOfFile:imgPath]; <<=== Leak : 9.1%
This is part of my code I don't understand why. What's the best way to resolve this issue?
NSString *imgPath;
UIImage *image;
IBNewsViewController *newsView = [[IBNewsViewController alloc] initWithURL:[tvLocal urlFlux] title:#"News" isEmission:NO];
[newsView setTitle:#"News"];
imgPath = [[NSBundle mainBundle] pathForResource:"news" ofType:#"png"];
image = [[UIImage alloc] initWithContentsOfFile:imgPath]; <<=== Leak : 9.1%
newsView.tabBarItem.image = image;
[image release];
image = nil;
UINavigationController* navNew = [[UINavigationController alloc] initWithRootViewController:newsView];
[newsView release];
newsView = nil;
EDIT:
No leak on iOS6.
Why it's leak on iOS7?
You should switch to the autoreleasing imageNamed: method. This has the added benefit of system level cacheing of the image.
NSString *imgPath;
UIImage *image;
IBNewsViewController *newsView = [[IBNewsViewController alloc] initWithURL:[tvLocal urlFlux] title:#"News" isEmission:NO];
[newsView setTitle:#"News"];
image = [UIImage imageNamed: #"news"];
newsView.tabBarItem.image = image;
UINavigationController* navNew = [[UINavigationController alloc] initWithRootViewController:newsView];
[newsView release];
newsView = nil;
To make life easier on yourself I'd switch your project to use ARC so you have less to worry about WRT memory management.
Replace this line
image = [[UIImage alloc] initWithContentsOfFile:imgPath];
With
image = [UIImage imageWithContentsOfFile:imgPath];
and check once.
First, switch to ARC. There is no single thing you can do on iOS that will more improve your code and remove whole classes of memory problems with a single move.
Beyond that, the code above does not appear to have a leak itself. That suggests that the actual mistake is elsewhere. There are several ways this could happen:
You're leaking the IBNewsViewController somewhere else
IBNewsViewController messes with its tabBarItem incorrectly and leaks that
You're leaking the UINavigationController somewhere else
You're retaining the tabBarItem.image somewhere else and failing to release it
Those are the most likely that I would hunt for. If you're directly accessing ivars, that can often cause these kinds of mistakes. You should use accessors everywhere except in init and dealloc. (This is true in ARC, but is absolutely critical without ARC.)
Leak detection is not perfect. There are all kinds of "abandoned" memory that may not appear to be a leak. I often recommend using Heapshot (now "Generation") analysis to see what other objects may be abandoned; that may give you a better insight into this leak.
Why differences in iOS 6 vs iOS 7? I suspect you have the same problem on iOS 6, but it doesn't look like a "leak", possibly because there is something caching the image that was removed in iOS 7. The cache pointer may make it look like it's not a leak to Instruments.
Speaking of which, do make sure to run the static analyzer. It can help you find problems.
And of course, switch to ARC.
For some reason, the allocation of two NSImageViews creates a huge memory leak.
Although dealloc method gets called in both NSImageViews.
I'm using ARC.
With NSImageViews
As soon as the following getter method is called, a lot of memory is used.
This would be ok, but it doesn't get deallocated when the window is being closed...
- (NSView *)animationView {
if (!_animationView) {
_animationView = [[NSView alloc] initWithFrame:self.bounds];
self.oldCachedImageView = [[NSImageView alloc] initWithFrame:self.bounds];
[_animationView addSubview:self.oldCachedImageView];
self.oldCachedImageView.wantsLayer = YES;
self.cachedImageView = [[NSImageView alloc] initWithFrame:self.bounds];
[_animationView addSubview:self.cachedImageView];
self.cachedImageView.wantsLayer = YES;
self.animationView.wantsLayer = YES;
}
return _animationView;
}
The first circle is when the getter method above is called.
The second is when the window is deallocated.
Without NSImageViews
Not with the two NSImageViews commented out.
- (NSView *)animationView {
if (!_animationView) {
_animationView = [[NSView alloc] initWithFrame:self.bounds];
/*
self.oldCachedImageView = [[NSImageView alloc] initWithFrame:self.bounds];
[_animationView addSubview:self.oldCachedImageView];
self.oldCachedImageView.wantsLayer = YES;
self.cachedImageView = [[NSImageView alloc] initWithFrame:self.bounds];
[_animationView addSubview:self.cachedImageView];
self.cachedImageView.wantsLayer = YES;
self.animationView.wantsLayer = YES;
*/
}
return _animationView;
}
No memory leak here...
Anyone an idea why this happens?
Use Instruments to trace the "Leaks". There is a Leaks tool in Instruments. Run it through the use of your app, all the way until you close your it completely. Make sure ARC isn't just deallocating it at a later time, which is possible. Keeping it in memory for a while allows for a faster retrieval if needed again.
However, if you do see a leak displayed, then there is an issue. Make sure there are no references to it you aren't noticing.
Use the leaks tool to figure out the memory location that is leaking if it displays some. Then try to match it with some object in your code. In other words, make sure it is the NSImageView that is leaking.
You can use:
NSLog(#"Location = %#",NSObjectHere);
To get the memory location. If it matches, then go back, once more, and find the reference you have to it. Unless it's an ARC bug, which is possible, though unlikely, then you are holding a reference to it somewhere.
For any more detail, please update your question with more code, no one can debug it for you without seeing more code.
Good luck!
EDIT
Tutorial explaining the use of the Leaks tool at http://mobileorchard.com/find-iphone-memory-leaks-a-leaks-tool-tutorial/
My goal is to make a video out of a short sequence of opengl frames (around 200 frames). So in order to do this, I use the following code to create a array of images:
NSMutableArray* images = [NSMutableArray array];
KTEngine* engine = [KTEngine sharedInstance]; //Opengl - based engine
for (unsigned int i = engine.animationContext.unitStart; i < engine.animationContext.unitEnd ; ++i)
{
NSLog(#"Render Image %d", i);
[engine.animationContext update:i];
[self.view setNeedsDisplay];
[images addObject:[view snapshot]];
}
NSLog(#"Total image rendered %d", [images count]);
[self createVideoFileFromArray:images];
So this works perfectly fine on simulator, but not on device (retina iPad). So my guess is that the device does not support so many UIimages (specially in 2048*1536). The crash always happends after 38 frames or so.
Now as for the solution, I thought to create a video for each 10 frames, and then attached them all together, but when can I know if I have enough space (is the autorelease pool drained?).
Maybe I should use a thread, process 10 images, and fire it again for the next 10 frames once it's over?
Any idea?
It seems quite likely that you're running out of memory.
To reduce memory usage, you could try to store the images as NSData using the PNG or JPG format instead. Both PNG's and JPG's are quite small when represented as data, but loading them into UIImage objects can be very memory consuming.
I would advice you to do something like below in your loop. The autorelease pool is needed to drain the returned snapshot on each iteration.
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
UIImage *image = [view snapshot];
NSData *imageData = UIImagePNGRepresentation(image);
[images addObject:imageData];
[pool release];
This of course requires your createVideoFileFromArray: method to handle pure image data instead of UIImage objects, but that should probably be feasible to implement.
I have a problem with compatibility of my application with an iOS5 b7 and GM versions.
The issue occurs in the next lines of code:
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!done);
App crashes with signal EXC_BAD_ACCESS after some iterations.
The number of passed iterations is random (from 2 till 7).
Also everything works quite well on iOS4 and iOS3.
The same issue occurs in Apple's sample: XMLPerformance Sample.
What do you think about this?
October 12th thousands of users of my app will upgrade to iOS5 and I don't want my app to be with such a strange error in the AppStore.
4 hours passed and I've found the problem. I will describe how I've resolved the problem in XMLPerformance sample.
The problem was in NSAutoreleasePool. There is #property (nonatomic, assign) NSAutoreleasePool *downloadAndParsePool;. When the app starts to download Top300 Paid Apps RSS new thread is created using [NSThread detachNewThreadSelector:#selector(downloadAndParse:) toTarget:self withObject:url];. So in that thread we should keep local autorelease pool. It is done in next way:
- (void)downloadAndParse:(NSURL *)url {
self.downloadAndParsePool = [[NSAutoreleasePool alloc] init];
// initializing internet connection and libxml parser.
if (rssConnection != nil) {
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!done);
}
// Release resources used only in this thread.
[downloadAndParsePool release];
self.downloadAndParsePool = nil;
}
So in downloadAndParse: everything looks fine. Now let's look in one method that is called when an item from RSS is parsed:
- (void)finishedCurrentSong {
// sending new item to delegate and other ...
countOfParsedSongs++;
// Periodically purge the autorelease pool. The frequency of this action may need to be tuned according to the
// size of the objects being parsed. The goal is to keep the autorelease pool from growing too large, but
// taking this action too frequently would be wasteful and reduce performance.
if (countOfParsedSongs == kAutoreleasePoolPurgeFrequency) {
[downloadAndParsePool release];
self.downloadAndParsePool = [[NSAutoreleasePool alloc] init];
countOfParsedSongs = 0;
}
}
As you see there lines :
[downloadAndParsePool release];
self.downloadAndParsePool = [[NSAutoreleasePool alloc] init];
So exactly that lines causes the exception. If I comment them everything works great.
But I decided not only to comment that lines but also replace NSAutoreleasePool in - (void)downloadAndParse:(NSURL *)url with #autorelease block as it is said that it is more efficient:
- (void)downloadAndParse:(NSURL *)url {
#autoreleasepool {
// initializing internet connection and libxml parser.
if (rssConnection != nil) {
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!done);
}
// Release resources used only in this thread.
}
}
Now everything works fine. The only problem that I haven't resolved is:
// Periodically purge the autorelease pool. The frequency of this action may need to be tuned according to the
// size of the objects being parsed. The goal is to keep the autorelease pool from growing too large, but
// taking this action too frequently would be wasteful and reduce performance.
So if anybody has any thoughts about this problem can post another one answer and may be try to explain more correctly the bug fix. I will be glad to accept that answer.
Thanks.
This looks like memory problem, please check Apple Technote QA1367 "Finding EXC_BAD_ACCESS bugs in a Cocoa project"
In your code, try this to crash as soon as possible:
[item release], item = nil;
It doesn't solve the problem, just makes the crash happen earlier and hopefully give you a more meaningful callstack to study.
If you're using multi-threading, well... You could try to print "current" thread id into console to verify that everything really is run in thread where you expect them to be running. Especially verify that all UI stuff is in main thread, even when such code is run as side-effect of other code (error popups, maybe).
#include <pthread.h>
- (void)myFunction
{
NSLog(#"Thread (%d)",
pthread_mach_thread_np(pthread_self()));
}
Run your app with Instruments, make sure to change memory verification to happen every 1 or 2 seconds. Slow, but yet again you want to get notified as close to the actual memory problem as possible.
Looking at your code: where did that "done" variable come from and who changes it's value and when? Now it looks pretty magical.
Also you could check the return value of runMode:beforeDate to make sure it was run. If the return value is NO, runLoop was not run at all. Maybe some other part of your code cannot handle such case?
Just my little contribution.
As I've got the same problem, I've discover that in iOS5, you don't need to have your own NSAutoreleasePool in a thread (used by performSelectorOnMainThread).
Then, in your code (a xml parser- same as me), I think you have to separate code from iOS4 and iOS5.
With iOS4, you need NSAutoreleasePool, but not with iOS5.
So, I am using [NSThread detachNewThreadSelector] to spawn a new thread and I am getting "autoreleased with no pool in place " errors in the console. I know this can happen if you fail to create an auto release pool, but the thing is, I am creating one. I use similar code in other parts of the same app and do NOT get these errors.
Here is the relevant code:
- (void) startThread:(NSString*)strURL
{
// start new thread to load image
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[NSThread detachNewThreadSelector:#selector(loadImageFromURL:) toTarget:self withObject:strURL];
[pool release];
}
- (void) loadImageFromURL:(NSString*)strURL
{
NSNumber* nn = [NSNumber numberWithInt:self.tag];
NSLog(#"loadURL: Tag number == %i", [nn intValue]);
// other code here actually does the work
}
Now, there was more code in loadImageFromURL which actually does the work (of loading an image from a remote server) - but the problem manifests itself without that code, so I've removed it (just so you don't think I have a pointless thread which does nothing!). I left in just one line of code which demonstrates the problem - it creates an autoreleased NSNumber object.
When this code runs, it reports this to the console:
__NSAutoreleaseNoPool(): Object 0x535c0e0 of class NSCFNumber autoreleased with no pool in place - just leaking
Of course, the real code creates many other AR objects and all of them get reported as well.
Would be grateful for any tips or pointers which might help!
Thanks!
When you create a new thread, you need to also create a new autorelease pool for it. In your case, that looks as simple as adding:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
at the beginning of loadImageFromURL: and
[pool drain];
at the end.
You probably don't need or want the pool you're creating in startThread:. Check out the Threading Programming Guide, particularly the "Writing Your Thread Entry Routine" section.
On your code, - (void) startThread:(NSString*)strURL is running in the main thread, while - (void) loadImageFromURL:(NSString*)strURL is running on the background thread you are detaching.
The main thread already has a NSAutoreleasePool, so the one you are creating in startThread: is probably unneeded. However, the background thread will not create a NSAutoreleasePool, so you'd need to create it yourself.
In your code, that would look like:
- (void) startThread:(NSString*)strURL
{
// start new thread to load image
[NSThread detachNewThreadSelector:#selector(loadImageFromURL:) toTarget:self withObject:strURL];
}
- (void) loadImageFromURL:(NSString*)strURL
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSNumber* nn = [NSNumber numberWithInt:self.tag];
NSLog(#"loadURL: Tag number == %i", [nn intValue]);
// other code here actually does the work
[pool drain];
}
Also, as #Carl Norum suggested, you should use drain instead of release when you are done using the autorelelase pool.
Solution for a similar problem but using ARC.
If using ARC, you could get an error "'NSAutoreleasePool' is unavailable: not available in automatic reference counting mode".
Use:
- (void) startThread:(NSString*)strURL
{
// start new thread to load image
[NSThread detachNewThreadSelector:#selector(loadImageFromURL:) toTarget:self withObject:strURL];
}
- (void) loadImageFromURL:(NSString*)strURL
{
#autoreleasepool {
NSNumber* nn = [NSNumber numberWithInt:self.tag];
NSLog(#"loadURL: Tag number == %i", [nn intValue]);
// other code here actually does the work
}
}