【问题标题】:EXC_BAD_ACCESS invoking a blockEXC_BAD_ACCESS 调用块
【发布时间】:2010-12-30 05:52:06
【问题描述】:

更新 |我已经使用面板​​上传了一个示例项目并在此处崩溃:http://w3style.co.uk/~d11wtq/BlocksCrash.tar.gz(我知道“选择...”按钮没有任何作用,我还没有实现它)。

更新 2 |刚刚发现我什至不需要在 newFilePanel 上调用任何东西就可以导致崩溃,我只需要在语句中使用它。

这也会导致崩溃:

[newFilePanel beginSheetModalForWindow:[windowController window] completionHandler:^(NSInteger result) {
    newFilePanel; // Do nothing, just use the variable in an expression
}];

似乎最后转储到控制台的内容有时是:“无法反汇编 dyld_stub_objc_msgSend_stret。”,有时是:“无法访问地址 0xa 的内存”。

我创建了自己的工作表(一个 NSPanel 子类),它尝试提供类似于 NSOpenPanel/NSSavePanel 的 API,因为它将自身呈现为工作表并在完成时调用一个块。

界面如下:

//
//  EDNewFilePanel.h
//  MojiBaker
//
//  Created by Chris Corbyn on 29/12/10.
//  Copyright 2010 Chris Corbyn. All rights reserved.
//

#import <Cocoa/Cocoa.h>

@class EDNewFilePanel;

@interface EDNewFilePanel : NSPanel <NSTextFieldDelegate> {
    BOOL allowsRelativePaths;

    NSTextField *filenameInput;

    NSButton *relativePathSwitch;

    NSTextField *localPathLabel;
    NSTextField *localPathInput;
    NSButton *chooseButton;

    NSButton *createButton;
    NSButton *cancelButton;
}

@property (nonatomic) BOOL allowsRelativePaths;

+(EDNewFilePanel *)newFilePanel;

-(void)beginSheetModalForWindow:(NSWindow *)aWindow completionHandler:(void (^)(NSInteger result))handler;
-(void)setFileName:(NSString *)fileName;
-(NSString *)fileName;
-(void)setLocalPath:(NSString *)localPath;
-(NSString *)localPath;
-(BOOL)isRelative;

@end

以及实现里面的关键方法:

-(void)beginSheetModalForWindow:(NSWindow *)aWindow completionHandler:(void (^)(NSInteger result))handler {
    [NSApp beginSheet:self
       modalForWindow:aWindow
        modalDelegate:self
       didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
          contextInfo:(void *)[handler retain]];
}

-(void)dismissSheet:(id)sender {
    [NSApp endSheet:self returnCode:([sender tag] == 1) ? NSOKButton : NSCancelButton];
}

-(void)sheetDidEnd:(NSWindow *)aSheet returnCode:(NSInteger)result contextInfo:(void *)contextInfo {
    ((void (^)(NSUInteger result))contextInfo)(result);
    [self orderOut:self];
    [(void (^)(NSUInteger result))contextInfo release];
}

如果我的块只是一个空主体的空操作,这一切都有效。关闭工作表时调用我的块。

EDNewFilePanel *newFilePanel = [EDNewFilePanel newFilePanel];
[newFilePanel setAllowsRelativePaths:[self hasSelectedItems]];
[newFilePanel setLocalPath:@"~/"];
[newFilePanel beginSheetModalForWindow:[windowController window] completionHandler:^(NSInteger result) {
    NSLog(@"I got invoked!");
}];

但是,当我尝试从块内部访问面板时,我会因 EXC_BAD_ACCESS 而崩溃。例如,这会崩溃:

EDNewFilePanel *newFilePanel = [EDNewFilePanel newFilePanel];
[newFilePanel setAllowsRelativePaths:[self hasSelectedItems]];
[newFilePanel setLocalPath:@"~/"];
[newFilePanel beginSheetModalForWindow:[windowController window] completionHandler:^(NSInteger result) {
    NSLog(@"I got invoked and the panel is %@!", newFilePanel);
}];

从调试器中不清楚原因是什么。堆栈上的第一项(零 0)只是说“??”并且没有列出任何内容。

堆栈中的下一项(1 和 2)分别是对 -endSheet:returnCode:-dismissSheet: 的调用。查看调试器中的变量,似乎没有任何问题/超出范围。

我原以为面板可能已被释放(因为它是自动释放的),但即使在创建后立即调用 -retain 也无济于事。

我是不是执行错了?

【问题讨论】:

    标签: objective-c macos cocoa objective-c-blocks nspanel


    【解决方案1】:

    当该对象不是实例变量时,您在一个方法中retain 一个参数而在另一个方法中release 它有点奇怪。

    我建议将beginSheetcompletionHandler 位设为实例变量。无论如何,您不能一次多次显示工作表,这样会更干净。

    此外,您的EXC_BAD_ACCESS 很可能来自您的beginSheet: 方法中的[handler retain] 调用。您可能正在调用此方法,例如(为简洁起见):

    [myObject doThingWithCompletionHandler:^{ NSLog(@"done!"); }];
    

    如果是这种情况,您必须 -copy 块而不是保留它。如上所述,该块存在于堆栈中。但是,如果该堆栈帧从执行堆栈中弹出,那么该块就消失了。 poof 任何稍后访问该块的尝试都将导致崩溃,因为您正在尝试执行不再存在且已被垃圾替换的代码。因此,您必须在块上调用 copy 以将其移动到堆中,在那里它可以在创建它的堆栈帧的生命周期之后存活。

    【讨论】:

    • 太棒了,谢谢!抄袭解决一切。另外,感谢关​​于我在哪里保留/释放的输入。即使对我来说也感觉很奇怪,但如果我没记错的话,我在苹果文档的某个地方遵循了一个模式(他们保留并释放了一个 NSNumber)。短暂的 ivar 可能风险较小。
    • 忘了补充,你的答案很清楚,在这种情况下你必须复制它才能保留它是有道理的,因为面板是异步调用的,堆栈当然会结束。
    • 我建议同时使用 Block_copy 和 Block_release
    【解决方案2】:

    尝试使用 __block 修饰符定义您的 EDNewFilePanel:

    __block EDNewFilePanel *newFilePanel = [EDNewFilePanel newFilePanel];
    

    这应该在block被调用的时候保留对象,可能是在Panel对象被释放之后。作为一个不相关的副作用,这也会使其在块范围内可变。

    【讨论】:

    • 试过了,但没有任何区别,它仍然崩溃。顺便说一句,NSOpenPanel 不需要使用任何__block。不过谢谢(PS:我在问题中添加了一个示例项目的链接)。
    • __block 与您描述的完全相反。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-03
    • 2015-02-17
    • 2011-02-25
    • 2017-03-23
    • 1970-01-01
    相关资源
    最近更新 更多