为什么这个简单的NSWINDOW创建代码触发了Autorelease池在ARC下关闭时崩溃



我在关闭时遇到了一个Autorelease池崩溃的问题,我将其简单地缩短到下面的小测试用例,仅创建一个窗口然后关闭它。如果将-fobjc-arc标志删除,则崩溃消失。在OS X 10.8.2,Clang 4.1(421.11.66)上运行。我希望有人对弧线有更深入了解的人能够启发我对这里发生的事情的启发 - 在节目中使用僵尸对象运行,是nswindow对象被释放得太多次,或者保留得不够多,但是我认为Arc是为了照顾所有这些吗?

堆栈跟踪是:

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

和测试案例的代码为:

// 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;
}

使用Instruments的僵尸配置文件表明,NSWINDOW对象通过呼叫close:将其放入自动发行池中。然后,一旦applicationDidFinishLaunching:完成并摧毁了NSWINDOW实例,ARC将正确最终以零的参考计数。但是,Autorelease池仍然知道现已倒闭的Nswindow实例,然后尝试在关闭时释放它,从而导致崩溃。

在ARC下进行管理的对象似乎是一个坏主意

可以通过添加[window setReleasedWhenClosed: NO];

弧只有将新创建的对象分配给具有大于当前范围的变量时,才会保留一个新创建的对象。否则,该对象将被泄漏。

在您的示例中,您正在通过调用Alloc来创建NSWindow的新实例,该实例将所有权临时转移到本地变量window。由于该变量在方法末尾不再存在,因此ARC必须插入release调用以避免泄漏窗口实例。结果,该实例不再由任何事物拥有,因此会自行交易。

要解决此问题,请用strong语义声明NSWindow类型的属性,然后将窗口实例传递到属性Setter方法(或将其直接分配给相应的实例变量 - 要么可以工作)。

编辑

要明确,您需要做的是将声明的属性(或至少一个实例变量)添加到MyApplication,例如

@interface MyApplication : NSApplication
@property (strong, nonatomic) NSWindow *window;
@end

然后,在您实施applicationDidFinishLaunching时,设置属性:

@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

使用ARC,您需要将属性用于假定会持续更长的事物。

要抽象私人属性,请在.m文件中使用匿名类别。

http://developer.apple.com/library/mac/#releasenotes/objectivec/rn-transitioningtoarc/introduction/introduction/introduction.html

这解决了:

@interface MyApplication : NSApplication
@property NSWindow *window;
@end
@implementation MyApplication
- (void) applicationDidFinishLaunching: (NSNotification *) note
{
  self.window = [[NSWindow alloc] initWithContentRect: NSMakeRect(100, 100, 100, 100)
                                                  styleMask: NSTitledWindowMask backing: NSBackingStoreBuffered defer: YES];
  [self.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;
}

希望有帮助。

最新更新