Y. Harris
Y. Harris

Reputation: 3

Objective-C Cocoa how to correctly use run loop in GCD

I'm not sure how to correctly use GCD in a run loop situation where the thread might need to be stopped. The problem starts from the outset, and how or where to use CGEventCallback (which is absent from my code). The stop button won't stop the loop, and I don't think my dispatch queue is setup properly -- along with the while loop creating a huge lag.

I've read top question-answers from the search, like this and this, but one is for iOS and the other isn't relevant. Could someone show me how to properly do this?

my code:

// .h

#import <Cocoa/Cocoa.h>

@interface AppDelegate : NSObject <NSApplicationDelegate> {

    IBOutlet NSTextField *textFieldBox;
    IBOutlet NSButton *stop;
}

@property (assign) IBOutlet NSWindow *window;

- (void)stop;

@end

// .m

#import "AppDelegate.h"

@implementation AppDelegate

BOOL isActive = FALSE;

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    [self mainMethod];
}

- (void)mainMethod {

    NSLog(@"loop started");
    isActive = TRUE;
    [self theArbitraryNonCompliantLoop];
    NSLog(@"doing other stuff");
}

- (void)stop {

    isActive = FALSE;
    return;
}

- (void)theArbitraryNonCompliantLoop {

    dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(backgroundQueue, ^{

        while (isActive) {
        for (NSUInteger i = 0; i < 1000000; i++) {
            [textFieldBox setStringValue:[NSString stringWithFormat:@"%lu",(unsigned long)i]];
            }
        }
    });
}

@end

Upvotes: 0

Views: 2526

Answers (4)

JeremyP
JeremyP

Reputation: 86651

Ignoring the name, the for loop needs to test isActive as well. That will solve the latency issue.

The UI update needs to be done on the main thread which is easy because you can just schedule a block on the main queue to do it.

- (void)theArbitraryNonCompliantLoop {

    dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(backgroundQueue, ^{

        while (isActive) 
        {
            for (NSUInteger i = 0; isActive && i < 1000000; i++) 
            {
                dispatch_async(dispatch_get_main_queue(),
                               ^{ [textFieldBox setStringValue:[NSString stringWithFormat:@"%lu",(unsigned long)i]] };
            }
        }
    });
}

There are still some issues here. I think, as it stands it will flood the main thread's run loop with events, so some throttling will be required. You might also consider some synchronisation for the inActive instance variable in case the compiler optimises it by pulling it into a register at the beginning of the method. Also, it will be subject to race conditions thanks to caching etc.

Upvotes: 4

cacau
cacau

Reputation: 3646

Why would you call an arbitrary method theRunLoop?

Either way, quoting Run Loops (Threading Programming Guide):

Both Cocoa and Core Foundation provide run loop objects to help you configure and manage your thread’s run loop. Your application does not need to create these objects explicitly; each thread, including the application’s main thread, has an associated run loop object. Only secondary threads need to run their run loop explicitly, however. The app frameworks automatically set up and run the run loop on the main thread as part of the application startup process.

Upvotes: 1

gnasher729
gnasher729

Reputation: 52546

Big mistake: You are changing a UI element on a background thread. That will cause all kinds of problems. Don't do that.

You seem to be quite confused what a runloop is. You are also trying to confuse people by calling something "theRunLoop" that just does stuff on a background thread. Your code has nothing to do with the runloop, and until you understand what a runloop is, better keep away from it.

Upvotes: 3

TheAmateurProgrammer
TheAmateurProgrammer

Reputation: 9392

My guess would be that your while loop is still on its first run. The 1000000 for loop is probably taking too long which is why it still seems like the loop is still running. To test it out put an NSLog after your for loop to see if it has exited it after you changed isActive to false.

Upvotes: 0

Related Questions