NSRunLoop- Clarification needed

2019-03-30 07:33发布

问题:

I am trying to understand the concept of RunLoops. I have read Apple developer's guide on RunLoops and to some extent I have understood the concept of RunLoops. To make my concept more clearer I wrote a very simple code which uses RunLoops in it. The code can be seen below.

    - (void)viewDidLoad
{
    [super viewDidLoad];
    thread = [[NSThread alloc] initWithTarget:self selector:@selector(testMethod) object:nil];
    [thread start];
}

- (void)testMethod {
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];

    NSLog(@"Thread Entered");

    NSMachPort* dummyPort = [[NSMachPort alloc] init];
    [[NSRunLoop currentRunLoop] addPort:dummyPort forMode:NSDefaultRunLoopMode];

    while(!exitThread) {
        NSLog(@"Thread did some work");
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }
    [[NSRunLoop currentRunLoop]
     removePort:dummyPort
     forMode:NSDefaultRunLoopMode];
    [dummyPort release];

    NSLog(@"Thread Exited");
    [pool drain];
}

- (IBAction)doDomeWorkOnBackgroundThread:(id)sender {
    [self performSelector:@selector(dummyMethod) onThread:thread withObject:nil waitUntilDone:NO];
}

- (IBAction)exitThread:(id)sender {
    [self performSelector:@selector(exitBackgroundThread) onThread:thread withObject:nil waitUntilDone:NO];
}

- (void)exitBackgroundThread {
    exitThread = YES;
}

- (void)dummyMethod {
    //Empty
}

In above code I am creating a background thread and on that background thread I am calling function testMethod . Inside testmethod I am running a while which checks for BOOL variable exitThread and runs the background thread's RunLoop using the - (BOOL)runMode: beforeDate: method of NSRunLoop. There are two IBActions which are attached to two buttons. As the name of the IBActions suggest one of them is to exitThread and other one is to wake up the thread and execute NSLog statement written in the while loop.

The above code runs just as I expected. Whenever doDomeWorkOnBackgroundThread method is executed thread wakes up from its runloop,performs the next iteration of the while loop, check for BOOL variable exitThread and on finding it value as false goes inside the while loop and executes the NSlog statement. Similarly When exitThread: method is executed the exitThread variable is set to true which causes the while loop and the thread to exit.

However I need few more clarifications :

1) If instead of using runMode: beforeDate: in the while loop , I use run or runUntilDate: method of NSRunLoop, then the thread is never exited when exitThread: method is executed. The exitBackgroundThread method is called on the background thread but the while loop never performs it's next iteration(as it does when I use runMode: beforeDate:) and hence the thread is never exited.

2) I tried changing exitBackgroundThread method to

- (void)exitBackgroundThread {
        exitThread = YES;
        CFRunLoopStop(CFRunLoopGetCurrent());
    }

Since exitBackgroundThread is executed on background thread CFRunLoopGetCurrent() should give the RunLoop of background thread. So this should ideally stop the run loop of the background thread regardless of whatever method of NSRunLoop I am using to start the RunLoop. So in any case thread must exit on call of above function. But it is just not happening.

I know I am missing something here and I am doing a fair amount of googling on my part to find answer to this question but just cant seem to find the right answer.

**EDIT

I have found this question which is very similar to my first query. It clears my first doubt to much extent.

回答1:

  1. The change you see when you use run or runUntilDate: instead of runMode:beforeDate: is expected. The documentation for runMode:beforeDate: says this:

    it returns after either the first input source is processed or limitDate is reached.

    There is an input source responsible for handling the performSelector:... requests. So when you send performSelector:..., the run loop processes an input source and then returns.

    On the other hand, the documentation for run says this:

    it runs the receiver in the NSDefaultRunLoopMode by repeatedly invoking runMode:beforeDate:. In other words, this method effectively begins an infinite loop that processes data from the run loop’s input sources and timers.

    So after the run loop processes the input source for your performSelector:... request, it waits for another input source to be ready. It doesn't return. Since it doesn't return, your while loop never gets a chance to test exitThread.

  2. Your attempt to use CFRunLoopStop is a good idea, but unfortunately, the documentation for run says this:

    If you want the run loop to terminate, you shouldn't use this method. Instead, use one of the other run methods and also check other arbitrary conditions of your own, in a loop.

    So you can't rely on CFRunLoopStop to make run return.

    Instead, you can drop to a lower level and use CFRunLoopRun to run the run loop, because CFRunLoopStop is documented to make that function return:

    This function forces rl to stop running and return control to the function that called CFRunLoopRun or CFRunLoopRunInMode for the current run loop activation.

    So try this to run your run loop:

    while(!exitThread) {
        NSLog(@"Thread did some work");
        CFRunLoopRun([NSRunLoop currentRunLoop].getCFRunLoop);
    }
    


回答2:

Documents of runMode:beforeDate: say:

Runs the loop once, blocking for input in the specified mode until a given date.

That mean it processes '@selector' input source only one time and then returns, not like run will continue to process next input source without returning.

However runUntilDate: will return after limit date if you code it as:

[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5.0]];