Custom Modal Window with Block Completion Handler

2019-03-30 11:06发布

问题:

I'm stuck!

I am trying to create a custom modal dialog. I would like it to perform similarly to the NSSavePanel using a block as a completion handler.

I've copied only the important snippets I think are needed.

@implementation ModalWindowController
    - (void)makeKeyAndOrderFront:(id)sender
                   modalToWindow:(NSWindow*)window
                      sourceRect:(NSRect)rect
               completionHandler:(void (^)(NSInteger result))handler {

        _handler = [handler retain];

        session = [NSApp beginModalSessionForWindow:[self window]];
        [[NSApplication sharedApplication] runModalSession:session];

        [[self window] makeKeyAndOrderFrontCentered:self expandingFromFrame:rect];
    }
    - (IBAction)okButtonPressed:(id)sender {
        [[self window] orderOut:self];
        _handler(NSOKButton);
        [NSApp endModalSession:session];
    }

@end

Now I can call this using the code:

[self.modalWindowController makeKeyAndOrderFront:self
                                   modalToWindow:[[self view] window]
                                      sourceRect:sr
                               completionHandler:^(NSInteger result) {
    NSLog(@"Inside Block");
    if ( result == NSOKButton ) {
        // do something interesting here
    }
}];
NSLog(@"Errg");

All goes well however, after the method makeKeyAndOrderFront:modalToWindow:sourceRect:completionHandler: has completed it does not block the thread, so "Errg" will be printed even though the user has not selected "ok" or "cancel". The modal window is displayed at this point, where the user clicks OK and the _handler block is then executed. However if I am trying to access local variables in the block, and the app crashes as everything has cleaned up already.

What is the best approach to blocking the main thread from the makeKeyAndOrderFront:... method? Is this the right approach to implementing a completion handler using blocks?

回答1:

Your line

_handler=[handler retain];

should be

_handler=[handler copy];

That should solve your problem, that the local variables are gone before the completion handler is called. [handler copy] takes care of the local variables referred to in the block, so that the local variables don't go away even after the flow of the program exited the method where you made the block.

Remember the following facts:

  1. The block instance captures the local variables referred inside the block.
  2. However, the block instance is on the stack. It will go away even you retain it, when the flow of the program go out of the scope {...} in which you create the block.
  3. So, you need to copy it, not just retain it, if you want to use the data afterwards, as you are doing here. Copying it automatically retains all the local object variables referred to from the block.
  4. You need to release it once you're done with it. It deallocates the memory for the block itself, and sends release message to the local object variables referred to. If you use GC, you don't have to care about this, though.

To understand more details of the block, I found the article here by Mike Ash very helpful.