objective-c - EXC_BAD_ACCESS 调用 block

标签 objective-c macos cocoa 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
}];

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

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

这是界面:

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

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

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

但是,一旦我尝试从 block 内部访问面板,我就会崩溃并返回 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 也无济于事。

我是不是执行错了?

最佳答案

当对象不是实例变量时,在一个方法中保留参数并在另一个方法中释放它有点奇怪。

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

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

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

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

关于objective-c - EXC_BAD_ACCESS 调用 block ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4560321/

相关文章:

objective-c - 基于 NSTableView 的 View 中的组合框

macos - 在 Macvim 中打开/安装 ctags(已安装 Homebrew 软件、oh-my-zsh、janus)

java - Mac OS X 上 Java 编程编辑器的问题

php - CakePHP 数据库连接 "Mysql"丢失,或无法创建

regex - NSRegularExpression 可选返回

iphone - 如何获取 iPhone 当前的网络速度?

ios - 未调用自定义单元格 didSelectRowAtIndexPath

ios - 对于 close 和 writeData 方法,NSOutputStream 线程安全吗?

objective-c - 正确使用窗口

iphone - 转换前。 2010-09-11T00 :00:00+01:00 format to NSDate