I'm interested in learning more about how best to handle memory management under tight loops with ARC. In particular, I've got an app I'm writing which has a while loop which rus for a really long time, and I've noticed that despite having implemented (what I believe to be) the best practices in ARC, the heap keeps growing boundlessly.
To illustrate the problem I'm having, I first set up the following test to fail on purpose:
while (true) {
NSMutableArray *array = [NSMutableArray arrayWithObject:#"Foo"];
[array addObject:#"bar"]; // do something with it to prevent compiler optimisations from skipping over it entirely
}
Running this code and profiling with the Allocations tool shows that the memory usage just endlessly increases. However, wrapping this in an #autoreleasepool as follows, immediately resolves the issue and keeps the memory usage nice and low:
while (true) {
#autoreleasepool {
NSMutableArray *array = [NSMutableArray arrayWithObject:#"Foo"];
[array addObject:#"bar"];
}
}
Perfect! This all seems to work fine -- and it even works fine (as would be expected) for non-autoreleased instances created using [[... alloc] init]. Everything works fine until I start involving any UIKit classes.
For example, let's create a UIButton and see what happens:
while (true) {
#autoreleasepool {
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame = CGRectZero;
}
}
Now, the memory usage increases ad infinitum -- effectively, it appears that the #autoreleasepool is having no effect.
So the question is why does #autoreleasepool work fine for the NSMutableArray and keep the memory in-check, but when applied to a UIButton the heap continues to grow?
Most importantly, how can I keep the heap from expanding endlessly when using UIKit classes in an endless loop like this, and what does this tell us about the best practices for ARC in while(true) or while(keepRunningForALongTime) style loops?
My gut feeling on this is (and I could be totally wrong) is that it's perhaps something about how the while (true) keeps the runloop from cycling, which is keeping the UIKit instances in memory rather than releasing them... But clearly I'm missing something in my understanding of ARC!
(And to eliminate an obvious cause, NSZombiedEnabled is not enabled.)
As to why the UI* objects grow without bounds? Internal implementation detail. Most likely, some kind of cache or runloop interaction that you are effectively disabling by blocking the main run loop.
Which brings me to the real answer:
Most importantly, how can I keep the heap from expanding endlessly
when using UIKit classes in an endless loop like this, and what does
this tell us about the best practices for ARC in while(true) or
while(keepRunningForALongTime) style loops?
How to fix it? Do not ever use a tight loop on the main thread and Do not ever block the main run loop.
Even if you were to figure out and workaround the UI* induced heap growth, your program still wouldn't work if you were to use a while(...) loop on the main thread. The entire design of iOS applications -- and Cocoa applications -- is that the main thread has a main run loop and that main run loop must be free to run.
If not? Your app will not be responsive (and will eventually be killed by the system) to user input and your drawing code is unlikely to work as expected (since the run loop coalesces dirty regions and draws them on demand in conjunction with the main thread, oft offloading to a secondary thread).
Speculation on my part here, but, it could boil down to the fact that UI-related objects especially tend to use GCD or similar (e.g. performSelectorOnMainThread:…) to ensure some actions happen on the main thread. This is as you suspect - the enqueued block or other unit of execution maintains a reference to the instance, waiting for its time in the runloop to execute, and never getting it.
As a rule it's bad to block the runloop. Once upon a time it used to be relatively common - drag tracking was often done this way (or effectively so, by running the runloop only in a special mode while the drag progressed). But it leads to weird interactions and even deadlocks, because lots of code isn't designed with the possibility in mind - especially in a GCD world where asynchronousity is king.
Remember that you can run the runloop explicitly if you like, inside your while loop, and while that's not quite identical to letting it run naturally, it usually works.
Right, I have done some more thinking about this, together with the great contributions from bbum and Wade Tegaskis regarding blocking the runloop, and realised that the way to mitigate this sort of issue is by letting the runloop cycle, by using the performSelector:withObject:afterDelay: which lets the runloop continue, whilst scheduling the loop to continue itself in the future.
For example, to return to my original example with the UIButton, this should now be rewritten as a method like this:-
- (void)spawnButton {
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame = CGRectZero;
[self performSelector:#selector(spawnButton) withObject:nil afterDelay:0];
}
This way, the method ends immediately and button is correctly released when it goes out of scope, but in the final line, spawnButton instructs the runloop to run spawnButton again in 0 seconds (i.e. as soon as possible), which in turn instructs the runloop to run... etc etc, you get the idea.
All you then need to do is call [self spawnButton] somewhere else in the code to get the cycle going.
This can also be solved similarly using GCD, with the following code which essentially does the same thing:
- (void)spawnButton {
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame = CGRectZero;
dispatch_async(dispatch_get_main_queue(), ^{
[self spawnButton];
});
}
The only difference here is that the method call is dispatched (asynchronously) onto the main queue (main runloop) using GCD.
Profiling it in Instruments I can now see that although the overall allocation memory is going up, the live memory remains low and static, showing that the runloop is cycling and the old UIButtons are being deallocated.
By thinking about runloops like this, and using performSelector:withObject:afterDelay: or GCD, there are actually a number of other instances (not just with UIKit) where this sort of approach can prevent unintentional "memory leaks" caused by runloop lockups (I use that in quotations because in this case I was being a UIKit rogue.. but there are other cases where this technique is useful.)
Related
- (void)btnInboxPressed
{
for (int j = 0; j < 100000; j++) {
[[UIButton alloc] init];
}
}
Why would this not show up as a memory leak in Instruments? If I alloc NSMutableString in the same fashion, Instruments flags a memory leak.
It may be because there is something somewhere that is still pointing to each button; still has a reachable reference to each button instance.
A "leak" is an object (or allocation) for which there is no way the program can every refer to it again. The address of said object no longer appears anywhere in the program.
There are, however, a multitude of ways you can accrete memory without it being counted as a leak.
Write-only caches are a classic failure pattern. So are registries where every instance is registered with some central scrutinizer. The central scrutinizer still has a valid reference -- thus not a leak -- even though you don't need the object again.
You are doing exactly what you should; "Holy cow! Why does instruments show that I have 10,000 UIButtons!?!?!? If I figure that out and make 'em go away, my memory use will drop significantly!!"
In this specific case, it might be that the autorelease pool still has a reference to the object. It might be that allocation and initialization of a UIButton causes it to be retain/autoreleased somewhere along the way.
No way to know without knowing where in the event loop that screenshot was grabbed.
I know this question looks like a dupe: I checked and it's not
In talking about NARC, the author of this blog says, "Personally, I like to immediately autorelease anything I NARC-ed, on the same line." This goes completely counter to all the examples I've seen on the Apple site and in books, where autorelease is only used when the object must be returned and cannot be released immediately. In general, the idea is that autorelease is memory intensive and can gum up your program (though it makes code cleaner). From Appress Beginning iPhone 3 Development:
these objects can have a detrimental
effect on your application’s memory
footprint. It is OK to use
autorelease, but try to use it only
when you really need to, not just to
save typing a line or two of code.
I am not asking if autorelease is worse than explicitly calling release (it is), but rather:
In most 'normal' situations on iPhone, just how bad is it to replace a later release with an earlier autorelease (in the same method)? Also, in what situations would it be absolutely prohibitive to do this?
My guess is that, compared to using a garbage collector (as MonoTouch apps do sucessfully), autorelease will hardly make a dent in your memory footprint, and that Vincent's advice it right on, and can make for cleaner code and less accidental memory-leaks.
There's nothing wrong with using autorelease, but when you allocate objects in a loop, you should always call release explicitly.
Using autorelease:
for (int i=0;i<1000;i++) {
NSString *s = [[[NSString alloc] init] autorelease];
}
// at this point, there are 1,000 unreleased string objects in memory
Using release:
for (int i=0;i<1000;i++) {
NSString *s = [[NSString alloc] init];
[s release];
}
// at this point, no string objects are "alive"
As you can see, you have to be really careful when using autorelease in loops.
You should be aware how autorelease works. Each thread in your application normally has a single autorelease pool. Objects can be registered in the pool. At the time they are registered, the pool determines the stackframe they belong to and will automatically pop the from the pool whenever that stackframe is left.
While this may seem costly (and it certainly is compared to direct retain/release), I don't think it even close to the cost a generation mark and sweep garbage collector can have.
Where autorelease really shines is in all situations where exceptions may be raised and there's no try/catch around. Autorelease is definitely preferable to a direct release in such cases.
There are, however, situations where you should avoid autorelease (the same goes for garabge collected environments where you should try to avoid these situations too). Creating temporary, autoreleased objects in a loop which runs a huge number of times is such a scenario, which puts significant stress on a garbage collector or the autorelease pool.
Replacing release with autorelease should be avoided in worker threads that are very simple and can live without the overhead of an autorelease pool. So the guideline is: Whenever you can avoid it, you should, whenever you're unsure autorelease.
I ran a few stress tests on my Iphone app. The results are below. I am wondering if I should be concerned and, if so, what I might do about it.
I set up a timer to fire once a second. Whenever the timer fired, the app requested some XML data from the server. When the data arrived, the app then parsed the data and redisplayed the affected table view.On several trials, the app averaged about 500 times through the loop before crashing.
I then removed the parsing and redisplay steps from the above loop. Now it could go about 800 times.
I set up a loop to repeatedly redisplay the table view, without downloading anything. As soon as one redisplay was completed, the next one began. After 2601 loops, the app crashed.
All of the above numbers are larger than what a user is likely to do.
Also, my app never lasts long at all when I try to run it on the device under instruments. So I can't get useful data that way. (But without instruments it lasts quite a while, as detailed above.)
I would say you need to be very concerned. The first rule of programming is that the user will never do what you expect.
Things to consider:
Accessor methods. Use them. Set up
properties for all attributes and
always access them with the
appropriate getter/setter methods:
.
object.property = some_other_object; -OR-
[object setProperty:some_other_object];
and
object = some_other_object.some_property;
object = [some_other_object some_property];
Resist the temptation to do things like:
property = some_other_object;
[property retain];
Do you get output from ObjectAlloc?
There are 4 tools from memory leaks,
performance and object allocations.
Are none of them loading?
What do you get when the app crashes?
EXEC_BAD_ACCESS or some other error?
Balanced retain (either alloc or
copy) and release. It is a good idea
to keep every alloc/copy balanced
with a release/autorelease in the
same method. If you use your
accessors ALL OF THE TIME, the need
for doing manual releases is seldom.
Autorelease will often hide a real
problem. It is possible Autorelease
can mask some tricky allocation
issues. Double check your use of
autorelease.
EDITED (Added based on your fault code)
Based on your above answer of "Program received signal: 0". This indicates that you have run out of memory. I would start by looking for instances that your code does something like:
myObject = [[MyClass alloc] init];
[someMutableArray addObject:myObject];
and you do not have the "release" when you put the new object into the array. If this array then gets released, the object, myObject, will become an orphan but hang around in memory anyway. The easy way to do this is to grep for all of your "alloc"/"copy" messages. Except under exceedingly rare conditions, there should be a paired "release""/autorelease" in the same function. More often than not, the above should be:
myObject = [[[MyClass alloc] init] autorelease];
[someMutableArray addObject:myObject];
I've been experiencing memory problems (the app will run for a couple of iterations, then receive low memory warning and finally be terminated) while working with NSInvocationOperation in a method called repeatedly by a NSTimer.
The method will be called every 1/4 of a second and I've narrowed down the source of the problem to the following test lines:
-(void)methodCalledByTimer {
NSInvocationOperation *o = [NSInvocationOperation alloc];
[o release];
}
Uncommenting these two lines (to produce an empty method) will prevent the memory problems from arising. Once they are in, memory usage will increase quite fast and finally the app will be terminated.
Can anybody explain what I'm doing wrong here? Do I have to do anything else to make sure, that the NSInvocationOperation object will be properly released?
Thank you very much in avance for your help.
Kind regards,
Michael.
A possible solution might be to just store your NSInvocationOperation somewhere else instead of creating and releasing one each time methodCalledByTimer is called.
I was having problems with NSCalendar where I would create and release one thousands of times to be used for some date work, but I then just created one calendar attached to the appDelegate and accessed that every time. Fixed a ton of memory leaks, and it's probably better than creating a new object every single time.
I believe the problem lies in how you allocate without initializing. The first of the buggy lines should read:
NSInvocationOperation *o = [[NSInvocationOperation alloc] initWithTarget:yourTarget selector:#selector(yourSelector) object:yourObjectOrNil];
Regarding mjdth's answer, I believe you should not attempt to reuse an invocation operation. From the documentation of NSOperation (the superclass of NSInvoationOperation):
"An operation object is a single-shot object—that is, it executes its task once and cannot be used to execute it again."
Furthermore, no Objective-C object should ever be initialized twice.
I'm nearing the end of a big iPhone project and whilst checking for memory leaks stumbled on this huge one. I implemented the sound following this tutorial:
http://www.gehacktes.net/2009/03/iphone-programming-part-6-multiple-sounds-with-openal/
Works a charm, a lot of people use it but I get a huge leak a start of the project when sound is initially loaded in. Below are the lines of code that start of the leak:
[[Audio sharedMyOpenAL] loadSoundWithKey:#"music" File:#"Music" Ext:#"wav" Loop:true];
[[Audio sharedMyOpenAL] loadSoundWithKey:#"btnPress" File:#"BtnPress" Ext:#"wav" Loop:false];
[[Audio sharedMyOpenAL] loadSoundWithKey:#"ting1" File:#"GlassTing1" Ext:#"wav" Loop:false];
etc. etc. it loads in 20 sounds altogether. And more specifically in the Audio.m file this chunk of code:
+ (Audio*)sharedMyOpenAL {
#synchronized(self) {
if (sharedMyOpenAL == nil) {
sharedMyOpenAL = [[self alloc] init]; // assignment not done here
}
}
return sharedMyOpenAL;
}
I am unsure how to resolve this and any help on the matter would be greatly appreciated.
Thanks.
Isn’t the “leak” simply the Audio singleton? I am not sure how the leak detection works, but from a certain viewpoint most singletons are leaks, since they only release memory after your application exits.
If this really is the case, then it depends on whether you need to release the memory used by the sounds. The memory usage should not go up, so you don’t have to worry about the “traditional leak” scenario where your application takes more and more memory until it gets killed. The code you are using does not seem to support sound unloading, so that if you want to release the memory, you’ll have to add that code yourself.
And a personal viewpoint: Writing a sound effect engine using a singleton is not a good design. Managing the sounds becomes a pain (this is exactly the problem you are facing), the singleton adds a lot of unnecessary boilerplate code, etc. I see no reason the sounds should not be simple separate objects with their own lifecycle – this is the way I’ve done it in my attempt at an OpenAL SFX engine. Of course, I could be wrong.
Update: I suppose the magic ‘assignment not done here’ is the key. The singleton code is taken from the Apple documentation, but somebody inserted an extra assignment. The sharedFoo method should look like this:
+ (MyGizmoClass*)sharedManager
{
#synchronized(self) {
if (sharedGizmoManager == nil) {
[[self alloc] init]; // assignment not done here
}
}
return sharedGizmoManager;
}
When you perform the extra assignment to self, you create the leak you are looking for.