4 // Perform a long task without blocking the main thread.
6 // Copyright (c) 2004-2005, Chaz 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 // 3. Neither the name of the BrokenZipper nor the names of its contributors may be
20 // used to endorse or promote products derived from this software without specific
21 // prior written permission.
23 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
24 // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
25 // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
26 // SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
28 // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
29 // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
34 // Web: http://www.brokenzipper.com/
35 // Email: chaz@brokenzipper.com
38 #import "ThreadedTask.h"
40 #import <objc/objc-runtime.h>
43 @interface ThreadedTask ( PrivateAPI
)
45 /* private initialization: designated initializer */
46 - (id)_initWithContext
:(id)context delegate
:(id)delegate
;
48 - (void)_runTask
:(NSArray
*)package
;
49 /* sent to the main thread to report task progress */
50 - (void)_taskReportProgress
:(NSNumber
*)progress
;
51 /* sent to the main thread to report a cancellation */
52 - (void)_taskDidCancel
:(id)dummy
;
53 /* sent to the main thread to report a completion */
54 - (void)_taskDidFinish
:(id)dummy
;
55 /* sent to the main thread to report a failure */
56 - (void)_taskDidFailWithErrorCode
:(NSNumber
*)errorCode
;
61 @implementation ThreadedTask
64 // #############################################################################
65 #pragma mark Initialization
66 // #############################################################################
70 return [self initWithTarget
:nil selector
:nil context
:nil delegate
:nil];
73 - (id)_initWithContext
:(id)context delegate
:(id)delegate
// DESIGNATED
75 if ( self = [super init
] ) {
76 [self setContext
:context
];
77 [self setDelegate
:delegate
];
79 _taskLock
= [[NSLock alloc
] init
];
84 - (id)initWithTarget
:(id)target selector
:(SEL)selector delegate
:(id)delegate
86 return [self initWithTarget
:target selector
:selector context
:nil delegate
:delegate
];
89 - (id)initWithTarget
:(id)target selector
:(SEL)selector context
:(id)context delegate
:(id)delegate
91 if ( self = [self _initWithContext
:context delegate
:delegate
] ) {
93 [self setTarget
:target selector
:selector
];
98 - (id)initWithFunction
:(int (*)(ThreadedTask
*, unsigned))function delegate
:(id)delegate
100 return [self initWithFunction
:function context
:nil delegate
:delegate
];
103 - (id)initWithFunction
:(int (*)(ThreadedTask
*, unsigned))function context
:(id)context delegate
:(id)delegate
105 if ( self = [self _initWithContext
:context delegate
:delegate
] ) {
106 // set initial values
107 [self setFunction
:function
];
114 // cancel any running task
117 // release retained objects
125 // #############################################################################
126 #pragma mark Accessor Methods
127 // #############################################################################
139 - (void)setTarget
:(id)target selector
:(SEL)selector
141 // don't do anything if the task is running
142 if ( [self isRunning
] ) {
146 if ( [target respondsToSelector
:selector
] ) {
147 // target & selector look good, save them
149 _selector
= selector
;
153 // bad target and/or selector, use nil
160 - (int (*)(id, unsigned))function
165 - (void)setFunction
:(int (*)(id, unsigned))function
167 // don't do anything if the task is running
168 if ( [self isRunning
] ) {
172 _function
= function
;
185 - (void)setContext
:(id)context
187 // don't do anything if the task is running
188 if ( [self isRunning
] ) {
203 - (void)setDelegate
:(id)delegate
205 _delegate
= delegate
;
208 - (void)setDelegateRunLoop
:(NSRunLoop
*)runloop modes
:(NSArray
*)modes
219 return _isTaskThreadRunning
;
223 // #############################################################################
224 #pragma mark Control Methods
225 // #############################################################################
229 // don't run if there is no iteration method/function to call
230 if ( [self isRunning
] ||
(!_target
&& !_function
) ) {
234 // set initial values
238 // use the default runloop
239 NSRunLoop
*current
= [NSRunLoop currentRunLoop
];
241 NSString
*currentMode
= [current currentMode
];
242 NSArray
*modes
= currentMode?
[NSArray arrayWithObject
:currentMode
]
243 : [NSArray arrayWithObjects
:NSDefaultRunLoopMode
,NSModalPanelRunLoopMode
,NSEventTrackingRunLoopMode
,NSConnectionReplyMode
,nil];
244 [self setDelegateRunLoop
:current modes
:modes
];
247 [self setDelegateRunLoop
:current modes
:_modes
];
251 // start the task thread!
252 _isTaskThreadRunning
= YES
;
253 [NSThread detachNewThreadSelector
:@selector(_runTask
:) toTarget
:self withObject
:nil];
259 if ( [self isRunning
] ) {
261 // this blocks until the task thread exits.
267 - (void)cancelWithoutWaiting
272 - (void)cancelAndRemoveDelegate
274 [self setDelegate
:nil];
275 [self cancelWithoutWaiting
];
279 // #############################################################################
280 #pragma mark Task Methods
281 // #############################################################################
283 - (void)reportProgress
:(int)progress
285 //[_runloop performSelector:@selector(_taskReportProgress:) target:self
286 // argument:[[NSNumber alloc] initWithInt:progress] order:0 modes:_modes];
287 [self performSelectorOnMainThread
:@selector(_taskReportProgress
:)
288 withObject
:[[NSNumber alloc
] initWithInt
:progress
] waitUntilDone
:NO
];
292 // #############################################################################
293 #pragma mark Private Methods
294 // #############################################################################
296 - (void)_runTask
:(NSArray
*)package
298 NSAutoreleasePool
*pool
;
303 // create the ever-so-important pool
304 pool
= [[NSAutoreleasePool alloc
] init
];
306 // set the lock the tells the main thread the task thread is running
309 // set first iteration
313 // enter the task loop
315 while ( !_doCancelTask
&& returnCode
== 1 ) {
316 NSAutoreleasePool
*loopPool
;
318 // do the actual work
319 loopPool
= [[NSAutoreleasePool alloc
] init
];
320 returnCode
= (int)objc_msgSend( _target
, _selector
, self, iteration
);
326 else if ( _function
) {
327 while ( !_doCancelTask
&& returnCode
== 1 ) {
328 NSAutoreleasePool
*loopPool
;
330 // do the actual work
331 loopPool
= [[NSAutoreleasePool alloc
] init
];
332 returnCode
= (int)_function( self, iteration
);
339 if ( _doCancelTask
) {
341 //[_runloop performSelector:@selector(_taskDidCancel:) target:self argument:nil order:iteration modes:_modes];
342 [self performSelectorOnMainThread
:@selector(_taskDidCancel
:) withObject
:nil waitUntilDone
:NO
];
344 else if ( returnCode
== 0 ) {
345 // report task completed
346 //[_runloop performSelector:@selector(_taskDidFinish:) target:self argument:nil order:iteration modes:_modes];
347 [self performSelectorOnMainThread
:@selector(_taskDidFinish
:) withObject
:nil waitUntilDone
:NO
];
351 [_runloop performSelector
:@selector(_taskDidFailWithErrorCode
:) target
:self
352 argument
:[[NSNumber alloc
] initWithInt
:returnCode
] order
:iteration modes
:_modes
];
353 //[self performSelectorOnMainThread:@selector(_taskDidFailWithErrorCode:)
354 // withObject:[[NSNumber alloc] initWithInt:returnCode] waitUntilDone:NO];
357 // allow the main thread to continue if it was blocking
358 _isTaskThreadRunning
= NO
;
365 - (void)_taskReportProgress
:(NSNumber
*)progress
367 if ( [_delegate respondsToSelector
:@selector(threadedTask
:reportedProgress
:)] ) {
368 [_delegate threadedTask
:self reportedProgress
:[progress intValue
]];
373 - (void)_taskDidCancel
:(id)dummy
375 if ( [_delegate respondsToSelector
:@selector(threadedTaskCancelled
:)] ) {
376 [_delegate threadedTaskCancelled
:self];
380 - (void)_taskDidFinish
:(id)dummy
382 if ( [_delegate respondsToSelector
:@selector(threadedTaskFinished
:)] ) {
383 [_delegate threadedTaskFinished
:self];
387 - (void)_taskDidFailWithErrorCode
:(NSNumber
*)errorCode
389 if ( [_delegate respondsToSelector
:@selector(threadedTask
:failedWithErrorCode
:)] ) {
390 [_delegate threadedTask
:self failedWithErrorCode
:[errorCode intValue
]];