--- /dev/null
+
+//
+// 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