Showing posts with label threads. Show all posts
Showing posts with label threads. Show all posts

June 4, 2009

Multiple Parameters to Threads

This hint is fairly trivial, but I was surprised I could not find any information on this little "problem". I wanted to pass more than one parameter to a thread entry method. The standard NSThread class only supports 0 or 1 parameter to be passed, as can be seen in this initializer:

- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument

Another way of starting a thread is with the method:

+ (void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument

But as you can see, this has the same limitation. The object parameter can be nil or some other parameter, but at most 1 parameter can be passed. Even the more modern NSOperation class has this same limitation and its remedy is pretty much the same as described here. There are two ways I see to overcome this limitation. One is more complicated than the other, but I will present both.  

Subclassing NSThread

The more complicated way is to subclass the NSThread class and override the main method to be your thread entry point. Since you create your own class, your initializer can accept any number of parameters, which you assign to instance variables. Then the main method can easily access this data:


@interface MyThread : NSThread
{
  NSString* data1;
  NSInteger data2;
}

- (id) initWithData1:(NSString*) data1 andData2:(NSInteger) data2;
- (void) main;

@end

@implementation MyThread

- (id) initWithData1:(NSString*) data1 andData2:(NSInteger) data2
{
  if((self = [super init])) {
    // save off our parameters
    self.data1 = data1;
    [self.data1 retain];

    self.data2 = data2;
  }

  return self;
}

- (void) main
{
  // no need to call [super main], as per the Apple reference docs
  // do whatever your thread needs to do
}

- (void) dealloc
{
  [data1 release];
}

@end


In order to kick off this thread, you do something like this:


// initialize it, passing all your parameters
MyThread* thread = [[MyThread alloc] initWithData1:@"hello there" andData2:23];

// now start it
[thread start];

// done with it
[thread release];


As you can see, this can be a bit involved, forcing you to create a new class, but it gets the job done.  

Stuffing Parameters

The other way is simpler, but at the same time a little clunkier. This way uses the simple initializer or the detach method as mentioned above. The way to get multiple parameters passed is to cheat: we pass a single container that holds all our parameters. Since this parameter is defined as id, it can be anything (as long as it is an object and not a primitive type). The perfect container is an NSArray. However, this is where things get a little clunky. First you have to create the array and populate it with your data. Worse is the fact that you can only put class objects into the array; scalars like integers will have to be converted to an object before adding.


// fire up a thread
[NSThread detachNewThreadSelector:@selector(myMain:)
                      toTarget:self
                    withObject:[NSArray arrayWithObjects:@"hello there",
                                        [NSNumber numberWithInt:23], nil]];


And then your thread entry method must pull the parameters back out of the array:


- (void) myMain:(NSArray*) parameters
{
 NSString* data1 = [parameters objectAtIndex:0];
 NSInteger data2 = [(NSNumber*) [parameters objectAtIndex:1] intValue];

 // the rest ...
}


Neither of these two solutions are perfect. The best solution, of course, is to write your thread code such that it doesn't need more than one parameter, but sometimes this is unavoidable. If any readers out there have any other ideas to solve this, I would love to hear them!

May 20, 2009

A Background Task Class

I am working on an app that makes XML calls to a web service. At first, I made the calls within the main thread and waited for the results to come back. Not surprising, when these calls took longer than usual, the user interface suffered. Basically, the UI locks up while waiting for a response from the web service. This is because the main thread is busy and cannot service UI-related events, such as touch events. What I had to do was make the web service calls in a separate thread so that the main thread can continue to service the UI. I coded up a few uses and I quickly discovered that my app wanted two different behaviors while making web service calls in the background. These behaviors are:
  1. The background task should operate transparently. It would only alert the user if the operation timed out,
  2. The user had to wait for the operation to complete, but user is given the option to cancel the background operation.
I decided to create a utility class to encapsulate these behaviors and make it all a little easier. Thus I created the BackgroundTask class, which I present here. What does it do? The BackgroundTask class is like a NSThread class; it allows you to spin off code in a separate thread. But this class is more powerful because it supports the two behaviors described above.

The Behaviors

First, let's discuss the desired behaviors in a little more detail. Behavior #1 listed above is for tasks that are low priority and don't require the user to wait for it to complete. The task is kicked off in the background, and the main thread retains control for the user to do whatever they need. They don't even know there is something going on in the background (except for the spinning network access icon in the status bar). The only feedback the user may get is if the operation timed out, meaning it failed. Each task created is given a timeout value. If the operation is not complete by this timeout period, then an alert message pops up that displays something like this:
The alert message displayed below "Operation failed" is also configurable when you create the task object. Behavior #2 is more of a blocking operation, but with the option to cancel it if the user feels it is taking too long. This is useful for higher-priority tasks that require the user to wait until it is complete. For example, if the user hit a refresh button to update the items displayed in a table, it kind of makes sense for the refresh to complete before continuing. When running a task like this, it displays a modal dialog like this:
I added a little optimization to this behavior as well. This dialog won't pop up for X number of seconds, where X is configurable. This is to avoid those situations where the background task completes almost immediately. When this happens, it was silly for the alert to pop up then disappear so fast that the user couldn't even read what it said. So the alert will only come up if the task takes longer than X seconds.

Instantiating

Since the BackgroundTask class supports two different behaviors, it also supports two different initializers. This first one initializes the object for behavior #1, where the task runs the background without any user interaction:
BackgroundTask* task = [[BackgroundTask alloc] initWithTarget:self
   selector:@selector(executeEmailPage:)
   argument:myparent.page
   timeout:40.0
   alertMsg:@"Attempt to email page timed out"]; 
The target/selector arguments specify the method to run in the new thread. The argument is optional data you can pass to the above method. Timeout specifies how long to give that thread before putting up an alert. And the final argument is the alert message to put up when the operation times out. This next initializer is for behavior #2, where the user can cancel the operation:
BackgroundTask* task = [[BackgroundTask alloc]
   initWithTarget:self
   selector:@selector(backgroundRefresh)
   argument:nil
   waitBeforeAlert:0.5
   title:@"Refreshing data"
   msg:@"Standby while refreshing pages"]; 

The target, selector, and argument parameters are the same as the first initializer. The waitBeforeAlert parameter specifies how long to give the operation to complete before putting up the modal box. The title and msg parameters allow you to configure the text displayed in the modal dialog.

Usage

To use the BackgroundTask object after you create it is very simple:
// start the background thread
[task start];

// release it since we don't need it anymore
[task release]; 

Getting The Code

The code for the BackgroundTask class is available on the Osmorphis google group. You can download the .zip archive here. Hopefully its usage is clear enough that you can easily stick the code in your own project and begin using it right away.