ThreadWorker


v0.7.1 - Throws a task onto another thread and notifies you when it's done.

Superclass: NSObject
Declared In: ThreadWorker.h

Discussion

Usage:

[ThreadWorker 
        workOn:self 
  withSelector:@selector(longTask:) 
    withObject:someData
didEndSelector:@selector(longTaskFinished:) ];

The ThreadWorker class was designed to be simple and to make multi-threading even simpler. You can offload tasks to another thread and be notified when the task is finished.

In this sense it is similar to the Java SwingWorker class, though the need for such a class in Cocoa and Objective-C is as different as the implementation.

Be sure to copy the ThreadWorker.h and ThreadWorker.m files to your project directory.

To see how to use this class, see the documentation for the "workOn" method below.

Consider adding the file release feed to your news aggregator (if you use one) to be notified of new releases: Follow the file release RSS feed...

I'm releasing this code into the Public Domain. Do with it as you will. Enjoy!

Original author: Robert Harder, rob@iharder.net

Change History

0.7.1 - Modified example to build in Xcode 3.2.
0.7 -
 o  Added ability to mark thread as cancelled.
 o  Changed the behavior when "longTask" takes a second argument.
    Instead of passing a proxy to the primary thread's "self"
    it passes a references to the ThreadWorker. The recommended way
    to pass information from the primary, or originating, thread
    is to use an NSDictionary to pass in the Things You'll Need.
    See the Controller.m example.
 o  Changed thread's termination behavior so that as soon as your
    "longTask:" is finished, the thread will exit. This means if
    you left anything on the NSRunLoop (or more likely an NSURL
    did it without your knowledge), it will get dumped.

0.6.2 - Moved [super dealloc] to the end of the dealloc method and
ensured "init" returns nil if it fails.

0.6.1 - Added [super dealloc] to the dealloc method and moved the
dealloc declaration out of the private API and into the
public .h file as it should be.

0.6 - Eliminated the need for the runSelectorInCallingThread method
by making a proxy to the target available to the task working
in the new thread. This makes for much less overhead.
Also changed static method signature from withArgument to withObject.

0.5.1 - Oops. Forget a necessary thread lock for the NSConnection creation.

0.5 - Uses NSConnection to communicate between threads, so although we
might have been thread-safe before (depending on whether or not
addTimer in NSRunLoop is thread-safe), we're definitely thread-safe
now - or so we think. =)
In the process we had to do away with the helper functions that took
a bit of hassle out using runSelectorInCallingThread with arguments
that are not objects. Sorry.

0.4.1 - Fixed some typos in commented sections.

0.4 - Released into the Public Domain. Enjoy!

0.3 - Permitted "workOn" method to accept a second argument of type
ThreadWorker* to allow for passing of the parent ThreadWorker
to the secondary thread. This makes it easy and reliable to
call other methods on the calling thread.

0.2 - Added runSelectorInCallingThread so that you could make calls
back to the main, i.e. calling, thread from the secondary thread.

0.1 - Initial release.



Methods

-cancelled

Returns whether or not someone has tried to cancel the thread.

-dealloc

Make sure we clean up after ourselves.

+description

Just a little note to say, "Good job, Rob!"

-markAsCancelled

Mark the ThreadWorker as cancelled.

+workOn:withSelector:withObject:didEndSelector:

Call this class method to work on something in another thread.


cancelled


Returns whether or not someone has tried to cancel the thread.

-(BOOL)cancelled; 
Discussion

Returns whether or not someone has tried to cancel the thread.


dealloc


Make sure we clean up after ourselves.

- (void) dealloc; 
Discussion

Make sure we clean up after ourselves.


description


Just a little note to say, "Good job, Rob!"

+ (NSString *)description; 
Discussion

Just a little note to say, "Good job, Rob!" to the original author of this Public Domain software.


markAsCancelled


Mark the ThreadWorker as cancelled.

-(void)markAsCancelled; 
Discussion

Marks the ThreadWorker as cancelled but doesn't actually cancel the thread. It is up to you to check whether or not the ThreadWorker is cancelled using a two-argument "longTask:..." method like so:

     - (id)longTask:(id)userInfo anyNameHere:(ThreadWorker *)tw
     {
         ...
         while(... && ![tw cancelled]){
             ...
         }
     }


workOn:withSelector:withObject:didEndSelector:


Call this class method to work on something in another thread.

+ (ThreadWorker *) workOn:(id)target withSelector:(SEL)selector withObject:(id)userInfo didEndSelector:(SEL)didEndSelector; 
Parameters
target

The object to receive the selector message. It is retained.

selector

The selector to be called on the target in the worker thread.

userInfo

An optional argument if you wish to pass one to the selector and target. It is retained.

didEndSelector

An optional selector to call on the target. Use the value 0 (zero) if you don't want a selector called at the end.

Return Value

Returns an autoreleased ThreadWorker that manages the worker thread.

Discussion

Example:

    NSDictionary *thingsIllNeed = [NSDictionary dictionaryWithObjectsAndKeys:
       self, @"self",
       myProgressIndicator, @"progress",
       myStatusField, @"status", nil];
 
    [ThreadWorker workOn:self 
                  withSelector:@selector(longTask:) 
                  withObject:thingsIllNeed
                  didEndSelector:@selector(longTaskFinished:)];
 

The longTask method in self will then be called and should look something like this:

    - (id)longTask:(id)userInfo
    {
        // Do something that takes a while and uses 'userInfo' if you want
        id otherSelf = [userInfo objectForKey:@"self"];
                    NSProgressIndicator *progress =
            (NSProgressIndicator *)[userInfo objectForKey:@"progress"];
                    NSTextField *status =
            (NSTextField *)[userInfo objectForKey:@"status"];
 
        return userInfo; // Will be passed to didEndSelector
    }    
 

Optionally you can have this "longTask" method accept a second argument which will be the controlling ThreadWorker instance which you can use to see if the ThreadWorker has been marked as cancelled. Your "longTask" method might then look like this:

    - (id)longTask:(id)userInfo anyNameHere:(ThreadWorker *)tw
   {
       ...
       while(... && ![tw cancelled]){
           ...
       }
   }
 

You can name the second parameter anythign you want. You only have to match it when you create the ThreadWorker like so:

    [ThreadWorker workOn:self 
                  withSelector:@selector(longTask: anyNameHere:) 
                  withObject:userInfo
                  didEndSelector:@selector(longTaskFinished:)];
 
 

When your longTask method is finished, whatever is returned from it will be passed to the didEndSelector (if it's not nil) as that selector's only argument. The didEndSelector will be called on the original thread, so if you launched the thread as a result of a user clicking on something, the longTaskFinished will be called on the main thread, which is what you need if you want to then modify any GUI components. The longTaskFinished method might look something like this, then:

    - (void)longTaskFinished:(id)userInfo
    {
        //Do something now that the thread is done
        // ...
    }    
 

Of course you will have to have imported the ThreadWorker.h file in your class's header file. The top of your header file might then look like this:

    import <Cocoa/Cocoa.h>
    import "ThreadWorker.h"
 

Enjoy.

Last Updated: Thursday, October 22, 2009