Question

I need to run a complex (ie long) task after the user clicks on a button. The button opens a sheet and the long running operation is started using dispatch_async and other Grand Central Dispatch stuff.

I've written the code and it works fine but I need help to understand if I've done everything correctly or if I've ignored (due to my ignorance) any potential problem.

The user clicks the button and opens sheet, the block contains the long task (in this example it only runs a for(;;) loop The block contains also the logic to close the sheet when task completes.

-(IBAction)openPanel:(id)sender {
    [NSApp beginSheet:panel
       modalForWindow:[self window]
        modalDelegate:nil
       didEndSelector:NULL
          contextInfo:nil];

    void (^progressBlock)(void);
    progressBlock = ^{

        running = YES; // this is a instance variable

        for (int i = 0; running && i < 1000000; i++) {
            [label setStringValue:[NSString stringWithFormat:@"Step %d", i]];
            [label setNeedsDisplay: YES];
        }
        running = NO;
        [NSApp endSheet:panel];
        [panel orderOut:sender];

    };

    //Finally, run the block on a different thread.
    dispatch_queue_t queue = dispatch_get_global_queue(0,0);
    dispatch_async(queue,progressBlock);
}

The panel contains a Stop button that allows user to stop the task before its completion

-(IBAction)closePanel:(id)sender {
    running = NO;
    [NSApp endSheet:panel];
    [panel orderOut:sender];
}
Was it helpful?

Solution

This code has a potential problem where it sets value of the status text. Basically all objects in AppKit are only allowed to be called from the main thread and can break in weird ways if they're not. You're calling the setStringValue: and setNeedsDisplay: methods on the label from whatever thread the global queue is running on. To fix this you should write the loop like so:

for (int i = 0; running && i < 1000000; i++) {
    dispatch_async(dispatch_get_main_queue(), ^{
        [label setStringValue:[NSString stringWithFormat:@"Step %d", i]];
        [label setNeedsDisplay: YES];
    });
}

This will set the label text from the main thread as AppKit expects.

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