EXC_BAD_ACCESS调用一个块

d11*_*wtq 3 macos cocoa objective-c objective-c-blocks nspanel

更新| 我已经使用面板​​上传了一个示例项目并在这里崩溃: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
}];
Run Code Online (Sandbox Code Playgroud)

看来最后抛出到控制台的东西有时就是:"无法反汇编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
Run Code Online (Sandbox Code Playgroud)

以及实现中的关键方法:

-(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];
}
Run Code Online (Sandbox Code Playgroud)

所有的作品都提供了我的块只是一个空体的无操作.当工作表被解雇时调用我的块.

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

但是当我尝试从块内部访问面板时,我崩溃了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);
}];
Run Code Online (Sandbox Code Playgroud)

调试器的原因并不清楚.堆栈上的第一项(零0)只是说"??" 而且没有列出任何内容.

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

我曾经想过可能面板已经发布(因为它是自动释放的),但是-retain在创建它之后立即调用它并没有帮助.

我是否实施了这个错误?

Dav*_*ong 12

retain一个方法release不是实例变量时,一个方法中的参数和另一个方法中的参数有点奇怪.

我建议completionHandler你把你的beginSheet东西做一个实例变量.这并不是说你无论如何都不能一次多次展示这张纸,而且这种方式会更清洁.

此外,您EXC_BAD_ACCESS最有可能来自[handler retain]您的beginSheet:方法中的电话.你可能用类似的东西调用这个方法(为了简洁起见):

[myObject doThingWithCompletionHandler:^{ NSLog(@"done!"); }];
Run Code Online (Sandbox Code Playgroud)

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