]> Dogcows Code - chaz/thecheat/blobdiff - ThreadedTask.m
The Cheat 1.2
[chaz/thecheat] / ThreadedTask.m
diff --git a/ThreadedTask.m b/ThreadedTask.m
new file mode 100644 (file)
index 0000000..19a91ab
--- /dev/null
@@ -0,0 +1,396 @@
+
+// 
+// ThreadedTask 0.3
+// Perform a long task without blocking the main thread.
+// 
+// Copyright (c) 2004-2005, Chaz McGarvey
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+// 
+// 1. Redistributions of source code must retain the above copyright notice, this list
+// of conditions and the following disclaimer.
+// 
+// 2. Redistributions in binary form must reproduce the above copyright notice, this
+// list of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+// 
+// 3. Neither the name of the BrokenZipper nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+// SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+// DAMAGE.
+// 
+// Web:   http://www.brokenzipper.com/
+// Email: chaz@brokenzipper.com
+// 
+
+#import "ThreadedTask.h"
+
+#import <objc/objc-runtime.h>
+
+
+@interface ThreadedTask ( PrivateAPI )
+
+/* private initialization: designated initializer */
+- (id)_initWithContext:(id)context delegate:(id)delegate;
+/* task method */
+- (void)_runTask:(NSArray *)package;
+/* sent to the main thread to report task progress */
+- (void)_taskReportProgress:(NSNumber *)progress;
+/* sent to the main thread to report a cancellation */
+- (void)_taskDidCancel:(id)dummy;
+/* sent to the main thread to report a completion */
+- (void)_taskDidFinish:(id)dummy;
+/* sent to the main thread to report a failure */
+- (void)_taskDidFailWithErrorCode:(NSNumber *)errorCode;
+
+@end
+
+
+@implementation ThreadedTask
+
+
+// #############################################################################
+#pragma mark Initialization
+// #############################################################################
+
+- (id)init
+{
+       return [self initWithTarget:nil selector:nil context:nil delegate:nil];
+}
+
+- (id)_initWithContext:(id)context delegate:(id)delegate // DESIGNATED
+{
+       if ( self = [super init] ) {
+               [self setContext:context];
+               [self setDelegate:delegate];
+               // create objects
+               _taskLock = [[NSLock alloc] init];
+       }
+       return self;
+}
+
+- (id)initWithTarget:(id)target selector:(SEL)selector delegate:(id)delegate
+{
+       return [self initWithTarget:target selector:selector context:nil delegate:delegate];
+}
+
+- (id)initWithTarget:(id)target selector:(SEL)selector context:(id)context delegate:(id)delegate
+{
+       if ( self = [self _initWithContext:context delegate:delegate] ) {
+               // set initial values
+               [self setTarget:target selector:selector];
+       }
+       return self;
+}
+
+- (id)initWithFunction:(int (*)(ThreadedTask *, unsigned))function delegate:(id)delegate
+{
+       return [self initWithFunction:function context:nil delegate:delegate];
+}
+
+- (id)initWithFunction:(int (*)(ThreadedTask *, unsigned))function context:(id)context delegate:(id)delegate
+{
+       if ( self = [self _initWithContext:context delegate:delegate] ) {
+               // set initial values
+               [self setFunction:function];
+       }
+       return self;
+}
+
+- (void)dealloc
+{
+       // cancel any running task
+       [self cancel];
+       [_taskLock release];
+       // release retained objects
+       [_context release];
+       [_modes release];
+
+       [super dealloc];
+}
+
+
+// #############################################################################
+#pragma mark Accessor Methods
+// #############################################################################
+
+- (id)target
+{
+       return _target;
+}
+
+- (SEL)selector
+{
+       return _selector;
+}
+
+- (void)setTarget:(id)target selector:(SEL)selector
+{
+       // don't do anything if the task is running
+       if ( [self isRunning] ) {
+               return;
+       }
+       
+       if ( [target respondsToSelector:selector] ) {
+               // target & selector look good, save them
+               _target = target;
+               _selector = selector;
+               _function = NULL;
+       }
+       else {
+               // bad target and/or selector, use nil
+               _target = nil;
+               _selector = NULL;
+       }
+}
+
+
+- (int (*)(id, unsigned))function
+{
+       return _function;
+}
+
+- (void)setFunction:(int (*)(id, unsigned))function
+{
+       // don't do anything if the task is running
+       if ( [self isRunning] ) {
+               return;
+       }
+       
+       _function = function;
+       if ( _function ) {
+               _target = nil;
+               _selector = NULL;
+       }
+}
+
+
+- (id)context
+{
+       return _context;
+}
+
+- (void)setContext:(id)context
+{
+       // don't do anything if the task is running
+       if ( [self isRunning] ) {
+               return;
+       }
+       
+       [context retain];
+       [_context release];
+       _context = context;
+}
+
+
+- (id)delegate
+{
+       return _delegate;
+}
+
+- (void)setDelegate:(id)delegate
+{
+       _delegate = delegate;
+}
+
+- (void)setDelegateRunLoop:(NSRunLoop *)runloop modes:(NSArray *)modes
+{
+       _runloop = runloop;
+       [modes retain];
+       [_modes release];
+       _modes = modes;
+}
+
+
+- (BOOL)isRunning
+{
+       return _isTaskThreadRunning;
+}
+
+
+// #############################################################################
+#pragma mark Control Methods
+// #############################################################################
+
+- (BOOL)run
+{
+       // don't run if there is no iteration method/function to call
+       if ( [self isRunning] || (!_target && !_function) ) {
+               return NO;
+       }
+
+       // set initial values
+       _doCancelTask = NO;
+       
+       if ( !_runloop ) {
+               // use the default runloop
+               NSRunLoop *current = [NSRunLoop currentRunLoop];
+               if ( !_modes ) {
+                       NSString *currentMode = [current currentMode];
+                       NSArray *modes = currentMode? [NSArray arrayWithObject:currentMode]
+                               : [NSArray arrayWithObjects:NSDefaultRunLoopMode,NSModalPanelRunLoopMode,NSEventTrackingRunLoopMode,NSConnectionReplyMode,nil];
+                       [self setDelegateRunLoop:current modes:modes];
+               }
+               else {
+                       [self setDelegateRunLoop:current modes:_modes];
+               }
+       }
+       
+       // start the task thread!
+       _isTaskThreadRunning = YES;
+    [NSThread detachNewThreadSelector:@selector(_runTask:) toTarget:self withObject:nil];
+       return YES;
+}
+
+- (void)cancel
+{
+       if ( [self isRunning] ) {
+               _doCancelTask = YES;
+               // this blocks until the task thread exits.
+               [_taskLock lock];
+               [_taskLock unlock];
+       }
+}
+
+- (void)cancelWithoutWaiting
+{
+       _doCancelTask = YES;
+}
+
+- (void)cancelAndRemoveDelegate
+{
+       [self setDelegate:nil];
+       [self cancelWithoutWaiting];
+}
+
+
+// #############################################################################
+#pragma mark Task Methods
+// #############################################################################
+
+- (void)reportProgress:(int)progress
+{
+       //[_runloop performSelector:@selector(_taskReportProgress:) target:self
+       //                               argument:[[NSNumber alloc] initWithInt:progress] order:0 modes:_modes];
+       [self performSelectorOnMainThread:@selector(_taskReportProgress:)
+                                                  withObject:[[NSNumber alloc] initWithInt:progress] waitUntilDone:NO];
+}
+
+
+// #############################################################################
+#pragma mark Private Methods
+// #############################################################################
+
+- (void)_runTask:(NSArray *)package
+{
+       NSAutoreleasePool *pool;
+
+       unsigned iteration;
+       int returnCode;
+       
+       // create the ever-so-important pool
+    pool = [[NSAutoreleasePool alloc] init];
+       
+       // set the lock the tells the main thread the task thread is running
+       [_taskLock lock];
+       
+       // set first iteration
+       iteration = 0;
+       returnCode = 1;
+
+       // enter the task loop
+       if ( _target ) {
+               while ( !_doCancelTask && returnCode == 1 ) {
+                       NSAutoreleasePool *loopPool;
+                       
+                       // do the actual work
+                       loopPool = [[NSAutoreleasePool alloc] init];
+                       returnCode = (int)objc_msgSend( _target, _selector, self, iteration );
+                       [loopPool release];
+                       
+                       iteration++;
+               }
+       }
+       else if ( _function ) {
+               while ( !_doCancelTask && returnCode == 1 ) {
+                       NSAutoreleasePool *loopPool;
+                       
+                       // do the actual work
+                       loopPool = [[NSAutoreleasePool alloc] init];
+                       returnCode = (int)_function( self, iteration );
+                       [loopPool release];
+                       
+                       iteration++;
+               }
+       }
+       
+       if ( _doCancelTask ) {
+               // report cancel
+               //[_runloop performSelector:@selector(_taskDidCancel:) target:self argument:nil order:iteration modes:_modes];
+               [self performSelectorOnMainThread:@selector(_taskDidCancel:) withObject:nil waitUntilDone:NO];
+       }
+       else if ( returnCode == 0 ) {
+               // report task completed
+               //[_runloop performSelector:@selector(_taskDidFinish:) target:self argument:nil order:iteration modes:_modes];
+               [self performSelectorOnMainThread:@selector(_taskDidFinish:) withObject:nil waitUntilDone:NO];
+       }
+       else {
+               // report error
+               [_runloop performSelector:@selector(_taskDidFailWithErrorCode:) target:self
+                                                argument:[[NSNumber alloc] initWithInt:returnCode] order:iteration modes:_modes];
+               //[self performSelectorOnMainThread:@selector(_taskDidFailWithErrorCode:)
+               //                                         withObject:[[NSNumber alloc] initWithInt:returnCode] waitUntilDone:NO];
+       }
+       
+       // allow the main thread to continue if it was blocking
+       _isTaskThreadRunning = NO;
+       [_taskLock unlock];
+       
+    [pool release];
+}
+
+
+- (void)_taskReportProgress:(NSNumber *)progress
+{
+       if ( [_delegate respondsToSelector:@selector(threadedTask:reportedProgress:)] ) {
+               [_delegate threadedTask:self reportedProgress:[progress intValue]];
+       }
+       [progress release];
+}
+
+- (void)_taskDidCancel:(id)dummy
+{
+       if ( [_delegate respondsToSelector:@selector(threadedTaskCancelled:)] ) {
+               [_delegate threadedTaskCancelled:self];
+       }
+}
+
+- (void)_taskDidFinish:(id)dummy
+{
+       if ( [_delegate respondsToSelector:@selector(threadedTaskFinished:)] ) {
+               [_delegate threadedTaskFinished:self];
+       }
+}
+
+- (void)_taskDidFailWithErrorCode:(NSNumber *)errorCode
+{
+       if ( [_delegate respondsToSelector:@selector(threadedTask:failedWithErrorCode:)] ) {
+               [_delegate threadedTask:self failedWithErrorCode:[errorCode intValue]];
+       }
+       [errorCode release];
+}
+
+
+@end
This page took 0.026693 seconds and 4 git commands to generate.