Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does this simple NSWindow creation code trigger an autorelease pool crash on shutdown under ARC?

I am having an issue with an autorelease pool crash on shutdown which I've reduced to the small test case below that simply creates a window and then closes it. The crash disappears if the -fobjc-arc flag is taken away. Running on OS X 10.8.2, Clang 4.1 (421.11.66). I am hoping that someone with a more in depth understanding of ARC can enlighten me as to what is going on here - running with zombie objects on shows that it is the NSWindow object that is getting released too many times, or not retained enough, but I thought ARC was meant to take care of all this?

The stack trace is:

0   libobjc.A.dylib                 0x00007fff8fad4f5e objc_release + 14
1   libobjc.A.dylib                 0x00007fff8fad4230 (anonymous namespace)::AutoreleasePoolPage::pop(void*) + 464
2   com.apple.CoreFoundation        0x00007fff99d22342 _CFAutoreleasePoolPop + 34
3   com.apple.Foundation            0x00007fff936e84fa -[NSAutoreleasePool drain] + 154
4   com.apple.Foundation            0x00007fff936effa0 _NSAppleEventManagerGenericHandler + 125
5   com.apple.AE                    0x00007fff93a5ab48 aeDispatchAppleEvent(AEDesc const*, AEDesc*, unsigned int, unsigned char*) + 307
6   com.apple.AE                    0x00007fff93a5a9a9 dispatchEventAndSendReply(AEDesc const*, AEDesc*) + 37
7   com.apple.AE                    0x00007fff93a5a869 aeProcessAppleEvent + 318
8   com.apple.HIToolbox             0x00007fff8d0c18e9 AEProcessAppleEvent + 100
9   com.apple.AppKit                0x00007fff8e95c916 _DPSNextEvent + 1456
10  com.apple.AppKit                0x00007fff8e95bed2 -[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:] + 128
11  com.apple.AppKit                0x00007fff8e953283 -[NSApplication run] + 517
12  Test                            0x00000001070e1d68 main + 152 (Test.mm:31)
13  libdyld.dylib                   0x00007fff8e10c7e1 start + 1

And the code for the test case is:

// Tested with `clang++ -fobjc-arc -g Test.mm -framework Cocoa -o Test && ./Test`

#import <Cocoa/Cocoa.h>

@interface MyApplication : NSApplication
@end
@implementation MyApplication
- (void) applicationDidFinishLaunching: (NSNotification *) note
{
    NSWindow * window = [[NSWindow alloc] initWithContentRect: NSMakeRect(100, 100, 100, 100)
                        styleMask: NSTitledWindowMask backing: NSBackingStoreBuffered defer: YES];

    [window close];

    [super stop: self];
}
@end

int main()
{
    @autoreleasepool
    {
        const ProcessSerialNumber psn = { 0, kCurrentProcess };
        TransformProcessType(&psn, kProcessTransformToForegroundApplication);
        SetFrontProcess(&psn);

        [MyApplication sharedApplication];
        [NSApp setDelegate: NSApp];

        [NSApp run];
    }

    return 0;
}
like image 542
Richard Viney Avatar asked Nov 12 '12 14:11

Richard Viney


2 Answers

ARC will only retain a newly created object if you assign it to a variable that has an extent greater than the current scope. Otherwise, the object would be leaked.

In your example, you're creating a new instance of NSWindow by calling alloc, which temporarily transfers ownership to the local variable, window. Since that variable ceases to exist at the end of method, ARC has to insert a release call to avoid leaking the window instance. As a result, the instance is no longer owned by anything, and therefore deallocates itself.

To fix this, declare a property of type NSWindow with strong semantics, and pass the window instance to the property setter method (or directly assign it to the corresponding instance variable -- either will work).

EDIT

To be clear, what you need to do is add a declared property (or at least an instance variable) to MyApplication, for example

@interface MyApplication : NSApplication

@property (strong, nonatomic) NSWindow *window;

@end

Then, in your implementation of applicationDidFinishLaunching, set the property:

@implementation MyApplication

- (void) applicationDidFinishLaunching: (NSNotification *) note
{
    NSWindow *window = [[NSWindow alloc] initWithContentRect:NSMakeRect(100, 100, 100, 100)
                        styleMask:NSTitledWindowMask backing:NSBackingStoreBuffered defer:YES];

    self.window = window;

    ...
}

@end
like image 30
jlehr Avatar answered Nov 12 '22 06:11

jlehr


Using Instruments' Zombies profile showed that the NSWindow object gets put into the autorelease pool by the call to close:. ARC then correctly ends up with a reference count of zero once applicationDidFinishLaunching: completes and destroys the NSWindow instance. However, the autorelease pool still knows about the now-defunct NSWindow instance and then tries to release it on shutdown, causing the crash.

Autoreleasing objects being managed under ARC seems like a bad idea unless the autorelease pool holds zeroing weak references to its objects, which it doesn't seem to be doing here.

The problem can be prevented by telling the window not to autorelease on close by adding [window setReleasedWhenClosed: NO];.

like image 176
Richard Viney Avatar answered Nov 12 '22 04:11

Richard Viney