// // GTMNSThread+Blocks.m // // Copyright 2012 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy // of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. // #import "GTMNSThread+Blocks.h" #import #import #if NS_BLOCKS_AVAILABLE @implementation NSThread (GTMBlocksAdditions) + (void)gtm_runBlockOnCurrentThread:(void (^)())block { block(); } - (void)gtm_performBlock:(void (^)())block { if ([[NSThread currentThread] isEqual:self]) { block(); } else { [self gtm_performWaitingUntilDone:NO block:block]; } } - (void)gtm_performWaitingUntilDone:(BOOL)waitDone block:(void (^)())block { [NSThread performSelector:@selector(gtm_runBlockOnCurrentThread:) onThread:self withObject:[[block copy] autorelease] waitUntilDone:waitDone]; } + (void)gtm_performBlockInBackground:(void (^)())block { [NSThread performSelectorInBackground:@selector(gtm_runBlockOnCurrentThread:) withObject:[[block copy] autorelease]]; } @end #endif // NS_BLOCKS_AVAILABLE enum { kGTMSimpleThreadInitialized = 0, kGTMSimpleThreadStarting, kGTMSimpleThreadRunning, kGTMSimpleThreadCancel, kGTMSimpleThreadFinished, }; @implementation GTMSimpleWorkerThread - (id)init { if ((self = [super init])) { runLock_ = [[NSConditionLock alloc] initWithCondition:kGTMSimpleThreadInitialized]; } return self; } - (void)dealloc { if ([self isExecuting]) { [self stop]; } [runLock_ release]; [super dealloc]; } - (void)setThreadDebuggerName:(NSString *)name { if ([name length]) { pthread_setname_np([name UTF8String]); } else { pthread_setname_np(""); } } - (void)main { [runLock_ lock]; if ([runLock_ condition] != kGTMSimpleThreadStarting) { // Don't start, we're already cancelled or we've been started twice. [runLock_ unlock]; return; } // Give ourself an autopool NSAutoreleasePool *localPool = [[NSAutoreleasePool alloc] init]; // Expose the current runloop so other threads can stop (but see caveat // below). NSRunLoop *loop = [NSRunLoop currentRunLoop]; runLoop_ = [loop getCFRunLoop]; if (runLoop_) CFRetain(runLoop_); // NULL check is pure paranoia. // Add a port to the runloop so that it stays alive. Without a port attached // to it, a runloop will immediately return when you call run on it. [loop addPort:[NSPort port] forMode:NSDefaultRunLoopMode]; // Name ourself [self setThreadDebuggerName:[self name]]; // We're officially running. [runLock_ unlockWithCondition:kGTMSimpleThreadRunning]; while (![self isCancelled] && [runLock_ tryLockWhenCondition:kGTMSimpleThreadRunning]) { [runLock_ unlock]; // We can't control nesting of runloops, so we spin with a short timeout. If // another thread cancels us the CFRunloopStop() we might get it right away, // if there is no nesting, otherwise our timeout will still get us to exit // in reasonable time. [loop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; [localPool drain]; localPool = [[NSAutoreleasePool alloc] init]; } // Exit [runLock_ lock]; [localPool drain]; if (runLoop_) CFRelease(runLoop_); runLoop_ = NULL; [runLock_ unlockWithCondition:kGTMSimpleThreadFinished]; } - (void)start { // Before we start the thread we need to make sure its not already running // and that the lock is past kGTMSimpleThreadInitialized so an immediate // stop is safe. [runLock_ lock]; if ([runLock_ condition] != kGTMSimpleThreadInitialized) { [runLock_ unlock]; return; } [runLock_ unlockWithCondition:kGTMSimpleThreadStarting]; [super start]; } - (void)cancel { // NSThread appears to not propagate [... isCancelled] to our thread in // this subclass, so we'll let super know and then use our condition lock. [super cancel]; [runLock_ lock]; switch ([runLock_ condition]) { case kGTMSimpleThreadInitialized: case kGTMSimpleThreadStarting: // Cancelled before we started? Transition straight to finished. [runLock_ unlockWithCondition:kGTMSimpleThreadFinished]; return; case kGTMSimpleThreadRunning: // If the thread has exited without changing lock state we detect that // here. Note this is a direct call to [super isExecuting] to prevent // deadlock on |runLock_| in [self isExecuting]. if (![super isExecuting]) { // Thread died in some unanticipated way, clean up on its behalf. if (runLoop_) { CFRelease(runLoop_); runLoop_ = NULL; } [runLock_ unlockWithCondition:kGTMSimpleThreadFinished]; return; } else { // We need to cancel the running loop. We'd like to stop the runloop // right now if we can (but see the caveat above about nested runloops). if (runLoop_) CFRunLoopStop(runLoop_); [runLock_ unlockWithCondition:kGTMSimpleThreadCancel]; return; } case kGTMSimpleThreadCancel: case kGTMSimpleThreadFinished: // Already cancelled or finished. There's an outside chance the thread // will have died now (imagine a [... dealloc] that calls pthread_exit()) // but we'll ignore those cases. [runLock_ unlock]; return; } } - (void)stop { // Cancel does the heavy lifting... [self cancel]; // If we're the current thread then the stop was called from within our // own runloop and we need to return control now. [... main] will handle // the shutdown on its own. if ([[NSThread currentThread] isEqual:self]) return; // From all other threads block till we're finished. Note that [... cancel] // handles ensuring we will either already be in this state or transition // there after thread exit. [runLock_ lockWhenCondition:kGTMSimpleThreadFinished]; [runLock_ unlock]; // We could still be waiting for thread teardown at this point (lock is in // the right state, but thread is not quite torn down), so spin till we say // execution is complete (our implementation checks superclass). while ([self isExecuting]) { usleep(10); } } - (void)setName:(NSString *)name { if ([self isExecuting]) { [self performSelector:@selector(setThreadDebuggerName:) onThread:self withObject:name waitUntilDone:YES]; } [super setName:name]; } - (BOOL)isCancelled { if ([super isCancelled]) return YES; BOOL cancelled = NO; [runLock_ lock]; if ([runLock_ condition] == kGTMSimpleThreadCancel) { cancelled = YES; } [runLock_ unlock]; return cancelled; } - (BOOL)isExecuting { if ([super isExecuting]) return YES; [runLock_ lock]; switch ([runLock_ condition]) { case kGTMSimpleThreadStarting: // While starting we may not be executing yet, but we'll pretend we are. [runLock_ unlock]; return YES; case kGTMSimpleThreadCancel: case kGTMSimpleThreadRunning: // Both of these imply we're running, but [super isExecuting] failed, // so the thread died for other reasons. Clean up. if (runLoop_) { CFRelease(runLoop_); runLoop_ = NULL; } [runLock_ unlockWithCondition:kGTMSimpleThreadFinished]; break; default: [runLock_ unlock]; break; } return NO; } - (BOOL)isFinished { if ([super isFinished]) return YES; if ([self isExecuting]) return NO; // Will clean up dead thread. BOOL finished = NO; [runLock_ lock]; if ([runLock_ condition] == kGTMSimpleThreadFinished) { finished = YES; } [runLock_ unlock]; return finished; } @end