10

我遇到了关机时自动释放池崩溃的问题,我将其简化为下面的小测试用例,它只是创建一个窗口然后关闭它。如果-fobjc-arc国旗被拿走,崩溃就会消失。在 OS X 10.8.2、Clang 4.1 (421.11.66) 上运行。我希望对 ARC 有更深入了解的人可以告诉我这里发生了什么 - 与僵尸对象一起运行表明它是 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;
}
4

3 回答 3

12

使用 Instruments 的 Zombies 配置文件显示 NSWindow 对象通过调用close:. applicationDidFinishLaunching:一旦完成并销毁 NSWindow 实例,ARC 就会正确地以零引用计数结束。但是,自动释放池仍然知道现已失效的 NSWindow 实例,然后在关闭时尝试释放它,从而导致崩溃。

在 ARC 下管理的自动释放对象似乎是个坏主意,除非自动释放池将对其对象的弱引用归零,它似乎在这里没有这样做。

可以通过告诉窗口不要在关闭时自动释放来防止这个问题[window setReleasedWhenClosed: NO];

于 2012-11-20T10:00:00.237 回答
3

如果您将新创建的对象分配给一个范围大于当前范围的变量,ARC 只会保留它。否则,对象将被泄露。

在您的示例中,您正在NSWindow通过调用 alloc 创建一个新实例,它会暂时将所有权转移到局部变量window. 由于该变量在方法结束时不再存在,ARC 必须插入release调用以避免泄漏窗口实例。结果,该实例不再由任何东西拥有,因此会自行释放。

要解决此问题,请声明一个NSWindow具有strong语义的类型属性,并将窗口实例传递给属性设置器方法(或直接将其分配给相应的实例变量——两者都可以)。

编辑

需要明确的是,您需要做的是将声明的属性(或至少一个实例变量)添加到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
于 2012-11-13T16:42:52.640 回答
0

使用 ARC,您需要将属性用于假设可以维持更长时间的事物。

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

http://developer.apple.com/library/mac/#releasenotes/ObjectiveC/RN-TransitioningToARC/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;
}

希望有帮助。

于 2012-11-20T09:34:03.417 回答