]> Dogcows Code - chaz/thecheat/blob - ThreadedTask.m
update contact information and project URL
[chaz/thecheat] / ThreadedTask.m
1
2 //
3 // ThreadedTask 0.3
4 // Perform a long task without blocking the main thread.
5 //
6 // Copyright (c) 2004-2005, Charles McGarvey
7 // All rights reserved.
8 //
9 // Redistribution and use in source and binary forms, with or without modification, are
10 // permitted provided that the following conditions are met:
11 //
12 // 1. Redistributions of source code must retain the above copyright notice, this list
13 // of conditions and the following disclaimer.
14 //
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.
18 //
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
28 // DAMAGE.
29 //
30
31 #import "ThreadedTask.h"
32
33 #import <objc/objc-runtime.h>
34
35
36 @interface ThreadedTask ( PrivateAPI )
37
38 /* private initialization: designated initializer */
39 - (id)_initWithContext:(id)context delegate:(id)delegate;
40 /* task method */
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;
50
51 @end
52
53
54 @implementation ThreadedTask
55
56
57 // #############################################################################
58 #pragma mark Initialization
59 // #############################################################################
60
61 - (id)init
62 {
63 return [self initWithTarget:nil selector:nil context:nil delegate:nil];
64 }
65
66 - (id)_initWithContext:(id)context delegate:(id)delegate // DESIGNATED
67 {
68 if ( self = [super init] ) {
69 [self setContext:context];
70 [self setDelegate:delegate];
71 // create objects
72 _taskLock = [[NSLock alloc] init];
73 }
74 return self;
75 }
76
77 - (id)initWithTarget:(id)target selector:(SEL)selector delegate:(id)delegate
78 {
79 return [self initWithTarget:target selector:selector context:nil delegate:delegate];
80 }
81
82 - (id)initWithTarget:(id)target selector:(SEL)selector context:(id)context delegate:(id)delegate
83 {
84 if ( self = [self _initWithContext:context delegate:delegate] ) {
85 // set initial values
86 [self setTarget:target selector:selector];
87 }
88 return self;
89 }
90
91 - (id)initWithFunction:(int (*)(ThreadedTask *, unsigned))function delegate:(id)delegate
92 {
93 return [self initWithFunction:function context:nil delegate:delegate];
94 }
95
96 - (id)initWithFunction:(int (*)(ThreadedTask *, unsigned))function context:(id)context delegate:(id)delegate
97 {
98 if ( self = [self _initWithContext:context delegate:delegate] ) {
99 // set initial values
100 [self setFunction:function];
101 }
102 return self;
103 }
104
105 - (void)dealloc
106 {
107 // cancel any running task
108 [self cancel];
109 [_taskLock release];
110 // release retained objects
111 [_context release];
112 [_modes release];
113
114 [super dealloc];
115 }
116
117
118 // #############################################################################
119 #pragma mark Accessor Methods
120 // #############################################################################
121
122 - (id)target
123 {
124 return _target;
125 }
126
127 - (SEL)selector
128 {
129 return _selector;
130 }
131
132 - (void)setTarget:(id)target selector:(SEL)selector
133 {
134 // don't do anything if the task is running
135 if ( [self isRunning] ) {
136 return;
137 }
138
139 if ( [target respondsToSelector:selector] ) {
140 // target & selector look good, save them
141 _target = target;
142 _selector = selector;
143 _function = NULL;
144 }
145 else {
146 // bad target and/or selector, use nil
147 _target = nil;
148 _selector = NULL;
149 }
150 }
151
152
153 - (int (*)(id, unsigned))function
154 {
155 return _function;
156 }
157
158 - (void)setFunction:(int (*)(id, unsigned))function
159 {
160 // don't do anything if the task is running
161 if ( [self isRunning] ) {
162 return;
163 }
164
165 _function = function;
166 if ( _function ) {
167 _target = nil;
168 _selector = NULL;
169 }
170 }
171
172
173 - (id)context
174 {
175 return _context;
176 }
177
178 - (void)setContext:(id)context
179 {
180 // don't do anything if the task is running
181 if ( [self isRunning] ) {
182 return;
183 }
184
185 [context retain];
186 [_context release];
187 _context = context;
188 }
189
190
191 - (id)delegate
192 {
193 return _delegate;
194 }
195
196 - (void)setDelegate:(id)delegate
197 {
198 _delegate = delegate;
199 }
200
201 - (void)setDelegateRunLoop:(NSRunLoop *)runloop modes:(NSArray *)modes
202 {
203 _runloop = runloop;
204 [modes retain];
205 [_modes release];
206 _modes = modes;
207 }
208
209
210 - (BOOL)isRunning
211 {
212 return _isTaskThreadRunning;
213 }
214
215
216 // #############################################################################
217 #pragma mark Control Methods
218 // #############################################################################
219
220 - (BOOL)run
221 {
222 // don't run if there is no iteration method/function to call
223 if ( [self isRunning] || (!_target && !_function) ) {
224 return NO;
225 }
226
227 // set initial values
228 _doCancelTask = NO;
229
230 if ( !_runloop ) {
231 // use the default runloop
232 NSRunLoop *current = [NSRunLoop currentRunLoop];
233 if ( !_modes ) {
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];
238 }
239 else {
240 [self setDelegateRunLoop:current modes:_modes];
241 }
242 }
243
244 // start the task thread!
245 _isTaskThreadRunning = YES;
246 [NSThread detachNewThreadSelector:@selector(_runTask:) toTarget:self withObject:nil];
247 return YES;
248 }
249
250 - (void)cancel
251 {
252 if ( [self isRunning] ) {
253 _doCancelTask = YES;
254 // this blocks until the task thread exits.
255 [_taskLock lock];
256 [_taskLock unlock];
257 }
258 }
259
260 - (void)cancelWithoutWaiting
261 {
262 _doCancelTask = YES;
263 }
264
265 - (void)cancelAndRemoveDelegate
266 {
267 [self setDelegate:nil];
268 [self cancelWithoutWaiting];
269 }
270
271
272 // #############################################################################
273 #pragma mark Task Methods
274 // #############################################################################
275
276 - (void)reportProgress:(int)progress
277 {
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];
282 }
283
284
285 // #############################################################################
286 #pragma mark Private Methods
287 // #############################################################################
288
289 - (void)_runTask:(NSArray *)package
290 {
291 NSAutoreleasePool *pool;
292
293 unsigned iteration;
294 #if MAC_OS_X_VERSION_10_5 <= MAC_OS_X_VERSION_MAX_ALLOWED
295 NSInteger returnCode;
296 #else
297 int returnCode;
298 #endif
299
300 // create the ever-so-important pool
301 pool = [[NSAutoreleasePool alloc] init];
302
303 // set the lock the tells the main thread the task thread is running
304 [_taskLock lock];
305
306 // set first iteration
307 iteration = 0;
308 returnCode = 1;
309
310 // enter the task loop
311 if ( _target ) {
312 while ( !_doCancelTask && returnCode == 1 ) {
313 NSAutoreleasePool *loopPool;
314
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 );
319 #else
320 returnCode = (int)objc_msgSend( _target, _selector, self, iteration );
321 #endif
322 [loopPool release];
323
324 iteration++;
325 }
326 }
327 else if ( _function ) {
328 while ( !_doCancelTask && returnCode == 1 ) {
329 NSAutoreleasePool *loopPool;
330
331 // do the actual work
332 loopPool = [[NSAutoreleasePool alloc] init];
333 returnCode = (int)_function( self, iteration );
334 [loopPool release];
335
336 iteration++;
337 }
338 }
339
340 if ( _doCancelTask ) {
341 // report cancel
342 //[_runloop performSelector:@selector(_taskDidCancel:) target:self argument:nil order:iteration modes:_modes];
343 [self performSelectorOnMainThread:@selector(_taskDidCancel:) withObject:nil waitUntilDone:NO];
344 }
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];
349 }
350 else {
351 // report error
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];
356 }
357
358 // allow the main thread to continue if it was blocking
359 _isTaskThreadRunning = NO;
360 [_taskLock unlock];
361
362 [pool release];
363 }
364
365
366 - (void)_taskReportProgress:(NSNumber *)progress
367 {
368 if ( [_delegate respondsToSelector:@selector(threadedTask:reportedProgress:)] ) {
369 [_delegate threadedTask:self reportedProgress:[progress intValue]];
370 }
371 [progress release];
372 }
373
374 - (void)_taskDidCancel:(id)dummy
375 {
376 if ( [_delegate respondsToSelector:@selector(threadedTaskCancelled:)] ) {
377 [_delegate threadedTaskCancelled:self];
378 }
379 }
380
381 - (void)_taskDidFinish:(id)dummy
382 {
383 if ( [_delegate respondsToSelector:@selector(threadedTaskFinished:)] ) {
384 [_delegate threadedTaskFinished:self];
385 }
386 }
387
388 - (void)_taskDidFailWithErrorCode:(NSNumber *)errorCode
389 {
390 if ( [_delegate respondsToSelector:@selector(threadedTask:failedWithErrorCode:)] ) {
391 [_delegate threadedTask:self failedWithErrorCode:[errorCode intValue]];
392 }
393 [errorCode release];
394 }
395
396
397 @end
This page took 0.048019 seconds and 4 git commands to generate.