更新|我使用面板上传了一个示例项目并在此处崩溃: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
也无济于事。
我是否实施了这个错误?
答案 0 :(得分:12)
当一个方法不是实例变量时,retain
一个方法中的参数和另一个方法中release
的参数有点奇怪。
我建议将completionHandler
内容的beginSheet
位设为实例变量。这并不是说你无论如何都不能一次多次展示这张纸,而且这种方式会更清晰。
此外,您的EXC_BAD_ACCESS
很可能来自[handler retain]
方法中的beginSheet:
来电。你可能用类似的东西来调用这个方法(为了简洁起见):
[myObject doThingWithCompletionHandler:^{ NSLog(@"done!"); }];
如果是这种情况,那么必须 -copy
块而不是保留它。如上所述,该块存在于堆栈中。但是,如果该堆栈帧从执行堆栈中弹出,那么该块就消失了。 poof 以后访问该块的任何尝试都将导致崩溃,因为您正在尝试执行不再存在且已被垃圾替换的代码。因此,您必须在块上调用copy
以将其移动到堆中,在堆中,它可以超出创建它的堆栈帧的生命周期。
答案 1 :(得分:-1)
尝试使用__block
修饰符定义您的EDNewFilePanel:
__block EDNewFilePanel *newFilePanel = [EDNewFilePanel newFilePanel];
这应该在调用块时保留对象,这可能是在释放Panel对象之后。作为一种无关的副作用,这也会使其在块范围内变得可变。