| // |
| // 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 <pthread.h> |
| #import <dlfcn.h> |
| |
| #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 |