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