4 // Perform a long task without blocking the main thread.
6 // Copyright (c) 2004-2005, Charles McGarvey
7 // All rights reserved.
9 // Redistribution and use in source and binary forms, with or without modification, are
10 // permitted provided that the following conditions are met:
12 // 1. Redistributions of source code must retain the above copyright notice, this list
13 // of conditions and the following disclaimer.
15 // 2. Redistributions in binary form must reproduce the above copyright notice, this
16 // list of conditions and the following disclaimer in the documentation and/or other
17 // materials provided with the distribution.
19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
20 // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
22 // SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
24 // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
25 // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
27 // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
31 #import "ThreadedTask.h"
33 #import <objc/objc-runtime.h>
36 @interface ThreadedTask ( PrivateAPI
)
38 /* private initialization: designated initializer */
39 - (id)_initWithContext
:(id)context delegate
:(id)delegate
;
41 - (void)_runTask
:(NSArray
*)package
;
42 /* sent to the main thread to report task progress */
43 - (void)_taskReportProgress
:(NSNumber
*)progress
;
44 /* sent to the main thread to report a cancellation */
45 - (void)_taskDidCancel
:(id)dummy
;
46 /* sent to the main thread to report a completion */
47 - (void)_taskDidFinish
:(id)dummy
;
48 /* sent to the main thread to report a failure */
49 - (void)_taskDidFailWithErrorCode
:(NSNumber
*)errorCode
;
54 @implementation ThreadedTask
57 // #############################################################################
58 #pragma mark Initialization
59 // #############################################################################
63 return [self initWithTarget
:nil selector
:nil context
:nil delegate
:nil];
66 - (id)_initWithContext
:(id)context delegate
:(id)delegate
// DESIGNATED
68 if ( self = [super init
] ) {
69 [self setContext
:context
];
70 [self setDelegate
:delegate
];
72 _taskLock
= [[NSLock alloc
] init
];
77 - (id)initWithTarget
:(id)target selector
:(SEL)selector delegate
:(id)delegate
79 return [self initWithTarget
:target selector
:selector context
:nil delegate
:delegate
];
82 - (id)initWithTarget
:(id)target selector
:(SEL)selector context
:(id)context delegate
:(id)delegate
84 if ( self = [self _initWithContext
:context delegate
:delegate
] ) {
86 [self setTarget
:target selector
:selector
];
91 - (id)initWithFunction
:(int (*)(ThreadedTask
*, unsigned))function delegate
:(id)delegate
93 return [self initWithFunction
:function context
:nil delegate
:delegate
];
96 - (id)initWithFunction
:(int (*)(ThreadedTask
*, unsigned))function context
:(id)context delegate
:(id)delegate
98 if ( self = [self _initWithContext
:context delegate
:delegate
] ) {
100 [self setFunction
:function
];
107 // cancel any running task
110 // release retained objects
118 // #############################################################################
119 #pragma mark Accessor Methods
120 // #############################################################################
132 - (void)setTarget
:(id)target selector
:(SEL)selector
134 // don't do anything if the task is running
135 if ( [self isRunning
] ) {
139 if ( [target respondsToSelector
:selector
] ) {
140 // target & selector look good, save them
142 _selector
= selector
;
146 // bad target and/or selector, use nil
153 - (int (*)(id, unsigned))function
158 - (void)setFunction
:(int (*)(id, unsigned))function
160 // don't do anything if the task is running
161 if ( [self isRunning
] ) {
165 _function
= function
;
178 - (void)setContext
:(id)context
180 // don't do anything if the task is running
181 if ( [self isRunning
] ) {
196 - (void)setDelegate
:(id)delegate
198 _delegate
= delegate
;
201 - (void)setDelegateRunLoop
:(NSRunLoop
*)runloop modes
:(NSArray
*)modes
212 return _isTaskThreadRunning
;
216 // #############################################################################
217 #pragma mark Control Methods
218 // #############################################################################
222 // don't run if there is no iteration method/function to call
223 if ( [self isRunning
] ||
(!_target
&& !_function
) ) {
227 // set initial values
231 // use the default runloop
232 NSRunLoop
*current
= [NSRunLoop currentRunLoop
];
234 NSString
*currentMode
= [current currentMode
];
235 NSArray
*modes
= currentMode?
[NSArray arrayWithObject
:currentMode
]
236 : [NSArray arrayWithObjects
:NSDefaultRunLoopMode
,NSModalPanelRunLoopMode
,NSEventTrackingRunLoopMode
,NSConnectionReplyMode
,nil];
237 [self setDelegateRunLoop
:current modes
:modes
];
240 [self setDelegateRunLoop
:current modes
:_modes
];
244 // start the task thread!
245 _isTaskThreadRunning
= YES
;
246 [NSThread detachNewThreadSelector
:@selector(_runTask
:) toTarget
:self withObject
:nil];
252 if ( [self isRunning
] ) {
254 // this blocks until the task thread exits.
260 - (void)cancelWithoutWaiting
265 - (void)cancelAndRemoveDelegate
267 [self setDelegate
:nil];
268 [self cancelWithoutWaiting
];
272 // #############################################################################
273 #pragma mark Task Methods
274 // #############################################################################
276 - (void)reportProgress
:(int)progress
278 //[_runloop performSelector:@selector(_taskReportProgress:) target:self
279 // argument:[[NSNumber alloc] initWithInt:progress] order:0 modes:_modes];
280 [self performSelectorOnMainThread
:@selector(_taskReportProgress
:)
281 withObject
:[[NSNumber alloc
] initWithInt
:progress
] waitUntilDone
:NO
];
285 // #############################################################################
286 #pragma mark Private Methods
287 // #############################################################################
289 - (void)_runTask
:(NSArray
*)package
291 NSAutoreleasePool
*pool
;
294 #if MAC_OS_X_VERSION_10_5 <= MAC_OS_X_VERSION_MAX_ALLOWED
295 NSInteger returnCode
;
300 // create the ever-so-important pool
301 pool
= [[NSAutoreleasePool alloc
] init
];
303 // set the lock the tells the main thread the task thread is running
306 // set first iteration
310 // enter the task loop
312 while ( !_doCancelTask
&& returnCode
== 1 ) {
313 NSAutoreleasePool
*loopPool
;
315 // do the actual work
316 loopPool
= [[NSAutoreleasePool alloc
] init
];
317 #if MAC_OS_X_VERSION_10_5 <= MAC_OS_X_VERSION_MAX_ALLOWED
318 returnCode
= (NSInteger
)objc_msgSend( _target
, _selector
, self, iteration
);
320 returnCode
= (int)objc_msgSend( _target
, _selector
, self, iteration
);
327 else if ( _function
) {
328 while ( !_doCancelTask
&& returnCode
== 1 ) {
329 NSAutoreleasePool
*loopPool
;
331 // do the actual work
332 loopPool
= [[NSAutoreleasePool alloc
] init
];
333 returnCode
= (int)_function( self, iteration
);
340 if ( _doCancelTask
) {
342 //[_runloop performSelector:@selector(_taskDidCancel:) target:self argument:nil order:iteration modes:_modes];
343 [self performSelectorOnMainThread
:@selector(_taskDidCancel
:) withObject
:nil waitUntilDone
:NO
];
345 else if ( returnCode
== 0 ) {
346 // report task completed
347 //[_runloop performSelector:@selector(_taskDidFinish:) target:self argument:nil order:iteration modes:_modes];
348 [self performSelectorOnMainThread
:@selector(_taskDidFinish
:) withObject
:nil waitUntilDone
:NO
];
352 [_runloop performSelector
:@selector(_taskDidFailWithErrorCode
:) target
:self
353 argument
:[[NSNumber alloc
] initWithInt
:returnCode
] order
:iteration modes
:_modes
];
354 //[self performSelectorOnMainThread:@selector(_taskDidFailWithErrorCode:)
355 // withObject:[[NSNumber alloc] initWithInt:returnCode] waitUntilDone:NO];
358 // allow the main thread to continue if it was blocking
359 _isTaskThreadRunning
= NO
;
366 - (void)_taskReportProgress
:(NSNumber
*)progress
368 if ( [_delegate respondsToSelector
:@selector(threadedTask
:reportedProgress
:)] ) {
369 [_delegate threadedTask
:self reportedProgress
:[progress intValue
]];
374 - (void)_taskDidCancel
:(id)dummy
376 if ( [_delegate respondsToSelector
:@selector(threadedTaskCancelled
:)] ) {
377 [_delegate threadedTaskCancelled
:self];
381 - (void)_taskDidFinish
:(id)dummy
383 if ( [_delegate respondsToSelector
:@selector(threadedTaskFinished
:)] ) {
384 [_delegate threadedTaskFinished
:self];
388 - (void)_taskDidFailWithErrorCode
:(NSNumber
*)errorCode
390 if ( [_delegate respondsToSelector
:@selector(threadedTask
:failedWithErrorCode
:)] ) {
391 [_delegate threadedTask
:self failedWithErrorCode
:[errorCode intValue
]];