#import /*! @class ThreadWorker @abstract v0.7.1 - Throws a task onto another thread and notifies you when it's done. @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.
*/ @interface ThreadWorker : NSObject { id _target; // The object whose selector will be called SEL _selector; // The selector that will be called in another thread id _argument; // The argument that will be passed to the selector SEL _didEndSelector; // Selector for final notice NSConnection *_callingConnection; // Connection used to safely communicate between threads NSPort *_port1; NSPort *_port2; NSConnection *_conn2; NSConditionLock *_cancelled; BOOL _endRunLoop; } /*! @method workOn:withSelector:withObject:didEndSelector: @param target The object to receive the selector message. It is retained. @param selector The selector to be called on the target in the worker thread. @param userInfo An optional argument if you wish to pass one to the selector and target. It is retained. @param 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. @result Returns an autoreleased ThreadWorker that manages the worker thread. @abstract Call this class method to work on something in another 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. */ + (ThreadWorker *) workOn:(id)target withSelector:(SEL)selector withObject:(id)userInfo didEndSelector:(SEL)didEndSelector; /*! @method markAsCancelled @abstract Mark the ThreadWorker as cancelled. @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]){
             ...
         }
     }
*/ -(void)markAsCancelled; /*! @method cancelled @abstract Returns whether or not someone has tried to cancel the thread. @discussion Returns whether or not someone has tried to cancel the thread. */ -(BOOL)cancelled; /*! @method dealloc @abstract Make sure we clean up after ourselves. @discussion Make sure we clean up after ourselves. */ - (void) dealloc; /*! @method description @abstract Just a little note to say, "Good job, Rob!" @discussion Just a little note to say, "Good job, Rob!" to the original author of this Public Domain software. */ + (NSString *)description; @end