X-Git-Url: https://git.dogcows.com/gitweb?p=chaz%2Fthecheat;a=blobdiff_plain;f=ThreadedTask.m;fp=ThreadedTask.m;h=19a91abcd666aef372c4d98fc1375aab48baaa8a;hp=0000000000000000000000000000000000000000;hb=d27548f80fe411fda2ee69c74a24eab4292267e9;hpb=e8d51183acdd2410a38dcf8f0efbf7c30cd6c581 diff --git a/ThreadedTask.m b/ThreadedTask.m new file mode 100644 index 0000000..19a91ab --- /dev/null +++ b/ThreadedTask.m @@ -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 + + +@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