Friday 29 April 2011

Thread Programming

Threading tutorial using NSthread in iPhone SDK (Objective-C)

This tutorial will show you how to use the NSThread in iPhone SDK to avoid loosing of the interface response.
This is good if you need to run some repetitive task on the background of the application or for some task, which may take long time to process.

Full source codes are included in the example aplication. (NSThread, detachNewThreadSelector, performSelectorOnMainThread).

Threading usually works in the same way in most programming languages. First you have the main thread, this one is created when you launch the application. Than you can create a different thread (can be started after some user interaction as a button pressed, etc, or automatically as a part of the program).

Because I am expecting that you already know how to connect elements from the interface builder to IBAction or IBOutlet, I am not going to cover this part. For more information’s please check the previous tutorials on XProgress.com about UIButton, UISwitch, UISlider or How to build the Navigation-based Application tutorial. Once you’ll finish any of these you’ll have pretty good idea "what the hell am I talking about" :)).

Let’s start with the less theoretical, and more interesting part of this tutorial. Yes, we are going to build our first multi-threaded application.

Start a new View-based application; call it "TutorialProject" to maintain the compatibility with the code in this tutorial. Open the TutorialProjectViewController.xib and set the main interface as it is on the next picture (You really need just the Thread part and the Test part). There is in both sections just one active UILabel (the one with the 0 (zero) in it). In the Tread part we have Progress view (UIProgressView) and button (UIButton).

The test part is here just because we want to verify that the interface is responding to touches while the other thread is running. It contains just that label with a value and slider (UISlider).


Now we have the user interface done ad we can move to the actual coding. Open the TutorialProjectViewController.h and create all the necessary IBOutlets for out interface.


@interface TutorialProjectViewController : UIViewController {

// ------ Tutorial code starts here ------

// Thread part
IBOutlet UILabel *threadValueLabel;
IBOutlet UIProgressView *threadProgressView;
IBOutlet UIButton *threadStartButton;

// Test part
IBOutlet UILabel *testValueLabel;

// ------ Tutorial code ends here ------

}

Now we need to create properties for our outlets (variables).

@property (nonatomic, retain) IBOutlet UILabel *threadValueLabel;
@property (nonatomic, retain) IBOutlet UIProgressView *threadProgressView;
@property (nonatomic, retain) IBOutlet UIProgressView *threadStartButton;
@property (nonatomic, retain) IBOutlet UILabel *testValueLabel;

 Next thing we need to do is to create IBAction’s. They are going to handle the events (actions) released when the button is pressed and when you’ll move the slider.

- (IBAction) startThreadButtonPressed:(UIButton *)sender;
- (IBAction) testValueSliderChanged:(UISlider *)sender;
The entire code for TutorialProjectViewController.h is here:


#import "uikit/uikit.h"

@interface TutorialProjectViewController : UIViewController {

// ------ Tutorial code starts here ------

// Thread part
IBOutlet UILabel *threadValueLabel;
IBOutlet UIProgressView *threadProgressView;
IBOutlet UIButton *threadStartButton;

// Test part
IBOutlet UILabel *testValueLabel;

// ------ Tutorial code ends here ------

}

// ------ Tutorial code starts here ------

@property (nonatomic, retain) IBOutlet UILabel *threadValueLabel;
@property (nonatomic, retain) IBOutlet UIProgressView *threadProgressView;
@property (nonatomic, retain) IBOutlet UIButton *threadStartButton;

@property (nonatomic, retain) IBOutlet UILabel *testValueLabel;


- (IBAction) startThreadButtonPressed:(UIButton *)sender;

- (IBAction) testValueSliderChanged:(UISlider *)sender;

// ------ Tutorial code ends here ------

// This function is for button which takes you to the xprogress.com website
- (IBAction) runXprogressComButton: (id) sender;

@end
Now open the TutorialProjectViewController.m file and synthesize the outlets.

@synthesize threadValueLabel, threadProgressView, testValueLabel, threadStartButton;

And after synthesizing don’t forget to release these from the memory in the dealloc function. You can find this one on the end of the file.

- (void)dealloc {

// ------ Tutorial code starts here ------

[threadValueLabel release];
[threadProgressView release];
[threadStartButton release];

[testValueLabel release];

// ------ Tutorial code ends here ------

[super dealloc];
}

Now we are going to do the actual threading. First of all we need to create a definition for our IBAction which will be called after the Start my thread button is pressed.

- (IBAction) startThreadButtonPressed:(UIButton *)sender {

threadStartButton.hidden = YES;
threadValueLabel.text = @"0";
threadProgressView.progress = 0.0;
[NSThread detachNewThreadSelector:@selector(startTheBackgroundJob) toTarget:self withObject:nil];

}

First we are hiding the button, because we don’t want users to press this button twice. On the second line we are setting the text to 0 (zero), resetting the progress bar on the third line and detaching a new thread on the fourth line, which calls the startTheBackgroundJob function.

Now we need to define the startTheBackgroundJob function.

- (void)startTheBackgroundJob {

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// wait for 3 seconds before starting the thread, you don't have to do that. This is just an example how to stop the NSThread for some time
[NSThread sleepForTimeInterval:3];
[self performSelectorOnMainThread:@selector(makeMyProgressBarMoving) withObject:nil waitUntilDone:NO];
[pool release];

}
On the first line we are setting a new autorelease pool. Every time autorelease is sent to an object, it is added to the outer-most autorelease pool. When the pool is drained, it simply sends -release to all the objects in the pool.

Autorelease pools are simply a convenience that allows you to defer sending -release until "later". That "later" can happen in several places, but the most common in Cocoa GUI apps is at the end of the current run loop cycle.

The second line is here just because I wanted to show you how to pause the thread for a while, in this case, for 3 seconds. Last line is actually starting a function makeMyProgressBarMoving, and because the waitUntilDone statement is on NO, this function will be actually running on the background.

- (void)makeMyProgressBarMoving {

float actual = [threadProgressView progress];
threadValueLabel.text = [NSString stringWithFormat:@"%.2f", actual];
if (actual < 1) {
threadProgressView.progress = actual + 0.01;
[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(makeMyProgressBarMoving) userInfo:nil repeats:NO];
}
else
threadStartButton.hidden = NO;
}


Here we are getting the actual value of the progress bar, setting a value for the label and if the value is less than 1 (which is the maxim value for the UIProgressView, equals to 100%) we are adding 0.01 to the progress and setting a new timer (NSTimer), which will call this function again in 0.5 second. If the progress bar is in 100% (1) we are going to show the button again.

To finish our application we need to add the code, which handles UISlider and we are done.

- (IBAction) testValueSliderChanged:(UISlider *)sender {

testValueLabel.text = [NSString stringWithFormat:@"%.2f", sender.value];

}

The entire source code for the TutorialProjectViewController.m is here:

#import "TutorialProjectViewController.h"

@implementation TutorialProjectViewController

@synthesize threadValueLabel, threadProgressView, testValueLabel, threadStartButton;

// ------ Tutorial code starts here ------

- (IBAction) startThreadButtonPressed:(UIButton *)sender {

threadStartButton.hidden = YES;
threadValueLabel.text = @"0";
threadProgressView.progress = 0.0;
[NSThread detachNewThreadSelector:@selector(startTheBackgroundJob) toTarget:self withObject:nil];

}

- (void)startTheBackgroundJob {

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// wait for 3 seconds before starting the thread, you don't have to do that. This is just an example how to stop the NSThread for some time
[NSThread sleepForTimeInterval:3];
[self performSelectorOnMainThread:@selector(makeMyProgressBarMoving) withObject:nil waitUntilDone:NO];
[pool release];

}

- (void)makeMyProgressBarMoving {

float actual = [threadProgressView progress];
threadValueLabel.text = [NSString stringWithFormat:@"%.2f", actual];

if (actual < 1) {
threadProgressView.progress = actual + 0.01;
[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(makeMyProgressBarMoving) userInfo:nil repeats:NO];
}
else
threadStartButton.hidden = NO;
}

- (IBAction) testValueSliderChanged:(UISlider *)sender
{
testValueLabel.text = [NSString stringWithFormat:@"%.2f", sender.value];
}

// ------ Tutorial code ends here ------ /*
// The designated initializer. Override to perform setup that is required before the view is loaded.

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])
{
// Custom initialization
}
return self;
}

*/ /* // Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView { }
*/ /* // Implement viewDidLoad to do additional setup after loading the view, typically from a nib.

- (void)viewDidLoad {
[super viewDidLoad];
}
*/ /* // Override to allow orientations other than the default portrait orientation.

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
*/
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.

[super didReceiveMemoryWarning];

// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload
{
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}

// This function is for button which takes you to the xprogress.com website

- (IBAction) runXprogressComButton: (id) sender
{ NSURL *url = [ [ NSURL alloc ] initWithString: @"http://www.xprogress.com/" ]; [[UIApplication sharedApplication] openURL:url];
}

- (void)dealloc 
{ // ------ Tutorial code starts here ------
[threadValueLabel release];
[threadProgressView release];
[threadStartButton release];
[testValueLabel release];
// ------ Tutorial code ends here ------
[super dealloc];
} @end

Now you need to open Interface builder and set all the IBOutlets and IBAction’s to the elements on your view. Connect threadStartButton with the Start my thread button, threadValueLabel with the 0 label above the progress bar, threadProgressView with progress bar and testValueLabel with the 0 (zero) UILabel above your testing UISlider.

Now connect Start my thread button’s Touch Up Inside action with startThreadButtonPressed., and testValueSliderChanged with the Value Changed event on your UISlider.
WooohoooOOO, the work is done. Launch the application in the simulator on the actual device, press the Start thread button and start using the UISlider to test that even if the application is running a thread on the background, you can still work with the application without loosing the respond ability in the UI.
Thanks for your time and I hope that I’ll see your next visit in my Google Analytics stats soon :))

No comments:

Post a Comment