Question

UPDATE | I've uploaded a sample project using the panel and crashing here: http://w3style.co.uk/~d11wtq/BlocksCrash.tar.gz (I know the "Choose..." button does nothing, I've not implemented it yet).

UPDATE 2 | Just discovered I don't even have to invoke anything on newFilePanel in order to cause a crash, I merely need to use it in a statement.

This also causes a crash:

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

It appears the last thing dumped to the console is sometimes this: "Unable to disassemble dyld_stub_objc_msgSend_stret.", and sometimes this: "Cannot access memory at address 0xa".

I've created my own sheet (an NSPanel subclass), that tries to provide an API similar to NSOpenPanel/NSSavePanel, in that it presents itself as a sheet and invokes a block when done.

Here's the interface:

//
//  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

And the key methods inside the implementation:

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

This all works provided my block is just a no-op with an empty body. My block in invoked when the sheet is dismissed.

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

But as soon as I try to access the panel from inside the block, I crash with EXC_BAD_ACCESS. For example, this crashes:

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

It's not clear from the debugger with the cause is. The first item (zero 0) on the stack just says "??" and there's nothing listed.

The next items (1 and 2) in the stack are the calls to -endSheet:returnCode: and -dismissSheet: respectively. Looking through the variables in the debugger, nothing seems amiss/out of scope.

I had thought that maybe the panel had been released (since it's autoreleased), yet even calling -retain on it right after creating it doesn't help.

Am I implementing this wrong?

Was it helpful?

Solution

It's a little odd for you to retain a parameter in one method and release it in another, when that object is not an instance variable.

I would recommend making the completionHandler bit of your beginSheet stuff an instance variable. It's not like you'd be able to display the sheet more than once at a time anyway, and it would be cleaner this way.

Also, your EXC_BAD_ACCESS is most likely coming from the [handler retain] call in your beginSheet: method. You're probably invoking this method with something like (for brevity):

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

If that's the case, you must -copy the block instead of retaining it. The block, as typed above, lives on the stack. However, if that stack frame is popped off the execution stack, then that block is gone. poof Any attempt to access the block later will result in a crash, because you're trying to execute code that no longer exists and has been replaced by garbage. As such, you must invoke copy on the block to move it to the heap, where it can live beyond the lifetime of the stack frame in which it was created.

OTHER TIPS

Try defining your EDNewFilePanel with the __block modifier:

__block EDNewFilePanel *newFilePanel = [EDNewFilePanel newFilePanel];

This should retain the object when the block is called, which may be after the Panel object is released. As an unrelated side-effect, this will make also make it mutable within the block scope.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top