More Fun With Autorelease
source link: https://www.mikeash.com/pyblog/more-fun-with-autorelease.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
I just hit a subtle but commonly known bug for the first time. I thought I'd share my fun with the world. Everybody reading this blog should know about autorelease pools and how they work in Cocoa. As everybody knows, every time you go through the event loop, Cocoa blows away the old pool and makes a new one for you, so that all of your autoreleased objects go away and your new ones go into a fresh pool. That way you never build up more objects than get produced during a single event loop cycle.
The key word is "event loop". In Apple's infinite wisdom, things that aren't real actual NSEvents don't trigger the pool.I'm currently working on an app that spends a lot of time in the background doing dark, unspeakable things with NSStreams on the main thread. I encountered a bug where one of my objects can get destroyed in the middle of handling a stream event, which left it open to getting other stream events after it was deallocated. (Apparently NSStreams can still send you stream events even after you've closed them, released them, and set their delegate to
nil
, but that's an entirely different problem for another day.)The obvious fix was to simply do
[[self retain] autorelease]
before making the problem call. And fix it it did, except instead of my dealloc
happening in the middle of my event handler, it never happened at all.Until I clicked on my app's dock icon.
At least the solution was easy. Post an
NSApplicationDefined
event in the stream event handler, and autoreleased objects get destroyed on schedule.The amazing part is that, from what I've heard, this bug has been there for a long time. I haven't used it, but it seems like CFRunLoopObserver would make it trivial to hook into the runloop at such a low level that you could drain the current pool if anything so much as blinks. And as we all know, Autorelease is Fast, so there should be no penalty in doing this.
How many of our apps sit quietly ticking away in the background, accumulating ever larger autorelease pools that only get drained once we bring them forward? It makes you wonder.
Hopefully this will be fixed in Leopard. I'm afraid to file a bug lest I get a "Behaves Correctly".
Comments:
For example, it was common on old systems that calling free() on a block of memory would not actually alter that memory until, at the soonest, the next call to malloc(). I believe some systems actually documented and guaranteed this. So a lot of programs ended up relying on being able to modify pointers to freed memory if it was done immediately. Some did it intentionally, some did it by accident. When those programs were moved to systems without that guarantee, they failed.
This autorelease thing is similar. The API only guarantees that autoreleased objects will survive at least until the caller of your method regains control (or an explicit autorelease pool pop occurs). However, in this case, autoreleased objects may persist beyond the current event and still be around when things like timers or delayed performs occur. Relying on them to be there is a bug, but it's the sort of bug which a lot of apps could have.
Could you be any more explicit about what function you'd use relying on something like that? I'm just intrigued. And also not anything near an expert in C++.
someIvar = [obj autoreleasedString]; // gets destroyed next event
[self performSelector:@selector(continuation) withObject:nil afterDelay:0];
- (void)continuation {
[obj doSomethingWith:someIvar];
}
It's possible this code will work correctly with the current situation, since the autorelease pool isn't destroyed until the next event, and the delayed perform doesn't qualify. Whereas if the problem were fixed to destroy the pool every time control returned to the runloop, the object in someIvar would be destroyed before continuation can execute and this code will crash.
Which I wouldn't say is nearly as bad as what many C++ books promote as good practice :-)
Thanks for your Blog postings. Most are enlightened.
Thanks
I have a QuickTime component, with a bunch of Obj-C objects inside. (ie: a 32-bit Core Foundation bundle.) (I am doing the work needed to switch over to the future Cocoa based media framework.)
Should I place a CFRunLoopObserver in there, to trigger a drain? I do see some accumulation when run through ObjectAlloc. (And a reasonable amount of Obj Leakage, too.)
Would a CoreFoundation Framework behave as a "Good Citizen" when doing this?
In other words, how do I get my CoreFoundation framework with embedded Obj-C objects to flush the autorelease pool, thus eliminating leakage?
bob.
Your best bet is probably to just surround all of your component's entry points with manually-created autorelease pools.
I have done that already, but Leaks does report some leakage.
I will dig around a bit more to see what is going on.
Thanks!
bob.
- (void)_kickEventLoop
{
NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined
location:NSMakePoint(0.0, 0.0) modifierFlags:0
timestamp:0 windowNumber:0 context:nil
subtype:0 data1:0 data2:0];
[NSApp postEvent:event atStart:FALSE];
}
and call that from notification handlers.
Comments RSS feed for this page
Add your thoughts, post a comment:
Spam and off-topic posts will be deleted without notice. Culprits may be publicly humiliated at my sole discretion.
Name:Web site:The Answer to the Ultimate Question of Life, the Universe, and Everything?Comment:Formatting: <i> <b> <blockquote> <code>. URLs are automatically hyperlinked.Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK