Chapter    6

Asynchronous Processing

This chapter covers how to add costly tasks to your applications without interrupting the main thread of operations. Objective-C supports many different options to solve this problem and this chapter covers the three most important: NSThread, Grand Central Dispatch, and NSOperationQueue.

The recipes in this chapter will show you how to:

  • Create a new thread for a background process
  • Send messages to the main thread in order to update the user interface
  • Lock threads to keep data structures in sync
  • Use Grand Central Dispatch (GCD) to implement asynchronous processing
  • Use operation queues to implement asynchronous processing using a more object-oriented approach
  • Use serial queues to protect data structures without locking threads to increase multithread performance

NOTE: The topics in this chapter can be complex because multithreading is a difficult problem for software developers. The underlying technology that facilitates multithreading has evolved rapidly over the last few years, which is why you will see a few alternatives here that you can use to solve the problems involved with asynchronous processing.

6.1 Running a Process in a New Thread

Problem

Your application needs to execute a task that will take a long time, but you would like the user interface to remain responsive and otherwise unaffected by the new operation.

Solution

Put the long task into a method and then use NSThread to create a thread separate from the main thread where the new operation is taking place.

How It Works

We refer to executable program like applications as processes when they are being executed by the operating system (here either iOS or OSX). Processes are made up of threads in which operations are executed at the same time. These operations could have happened at the same time on different processors or on the same processor using a time-sharing strategy (each thread takes a turn using the computer’s processor).

All programs have at least one primary thread referred to as the main thread. Applications use the main thread to manage the user interface, but there may be other threads working at the same time doing tasks that are not directly related to the user interface or generally part of the main thread.

NOTE: To follow along with this recipe, you will need to have a Single View iPhone application with a button and an activity view. For general information on how to use iPhone applications and user controls, see Recipe 1.12 and Recipe 1.13. See the complete code listings here for the details on setting up the user interface.

To create a new thread in Objective-C, you can use the NSThread class, but first you need to put all the code that you want to run on the separate thread in a method.

-(void) bigTask{
    for(int i=0;i<40000;i++){
        NSString *newString = [NSString stringWithFormat:@"i = %i", i];
        NSLog(@"%@", newString);
    }
    [self.myActivityIndicator stopAnimating];
}

This method, bigTask, loops 40,000 times. During each loop a new string is constructed and then written out to the log. After all this, a message is sent to the activity indicator to stop spinning, which indicates that the task is complete.

Autorelease

There is one more thing that you should do with bigTask before you move on. It has to do with memory management, which is covered in more detail in Chapter 8, and is important when you are dealing with threads. You need to put the code in bigTask into an autoreleasepool. Autoreleasepool allows Objective-C to use memory resources and then dispose of the resources as needed. Every thread requires an autoreleasepool or you will find memory leaks in your app.

To add an autoreleasepool to bigTask, enclose all of bigTask’s code in a block starting with the @autorelease keyword.

-(void) bigTask{
    @autoreleasepool {
        for(int i=0;i<40000;i++){
            NSString *newString = [NSString stringWithFormat:@"i = %i", i];
            NSLog(@"%@", newString);
        }
        [self.myActivityIndicator stopAnimating];
    }
}

You could simply execute this method directly by assigning bigTask as an action associated with a touch event to a button. However, if you did so, your user interface would be completely unresponsive until the task was complete.

A better way to execute the bigTask is to create a new method for the task of setting up the user interface and then executing the bigTask. You will ultimately assign this method to a button action. Call it bitTaskAction and start the method like this:

-(void)bigTaskAction{
    [self.myActivityIndicator startAnimating];
}

So far, bigTaskAction just sets up the user interface by sending a message to the activity indicator to start spinning. To execute the big task, use the NSThread class method detachNewThreadSelector:toTarget:withObject:.

-(void)bigTaskAction{
    [self.myActivityIndicator startAnimating];

    [NSThread detachNewThreadSelector:@selector(bigTask)
                             toTarget:self
                           withObject:nil];

}

This will make a new thread and execute the code in the method that you specify with the @selector keyword. You can also specify the target object, which must be the object where the method from the first parameter is located. If the method accepts a parameter, then you can pass an object using the last parameter in the method. Yours doesn’t require a parameter so you simply pass nil.

To make this work with an application, assign the method that spawns the thread as an action to a user control. If you add this to an iPhone app, make sure to add bigTaskAction to the action method.

[self.myButton addTarget:self
                  action:@selector(bigTaskAction)
        forControlEvents:UIControlEventTouchUpInside];

When a user touches a button on an app like this, the big task will execute in its own thread and not interrupt the user interface at all. See Listings 6-1 through 6-4 for the code.

The Code

Listing 6-1. AppDelegate.h

#import <UIKit/UIKit.h>

@class ViewController;

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@property (strong, nonatomic) ViewController *viewController;

@end

Listing 6-2. AppDelegate.m

#import "AppDelegate.h"

#import "ViewController.h"

@implementation AppDelegate

@synthesize window = _window;
@synthesize viewController = _viewController;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:  Image
(NSDictionary *)launchOptions{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.viewController = [[ViewController alloc] initWithNibName:@"ViewController"
                                                           bundle:nil];
    self.window.rootViewController = self.viewController;
    [self.window makeKeyAndVisible];

    return YES;
}

@end

Listing 6-3. ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@property(strong) UIButton *myButton;
@property(strong) UIActivityIndicatorView *myActivityIndicator;

@end

Listing 6-4. ViewController.m

#import "ViewController.h"

@implementation ViewController
@synthesize myButton, myActivityIndicator;

-(void) bigTask{
    @autoreleasepool {
        for(int i=0;i<40000;i++){
            NSString *newString = [NSString stringWithFormat:@"i = %i", i];
            NSLog(@"%@", newString);
        }
        [self.myActivityIndicator stopAnimating];
    }
}
/*
 //do task without using a new thread (watch UI to see how this works)
 -(void)bigTaskAction{
 [self.myActivityIndicator startAnimating];
 [self bigTask];
 }
 */

//do task by detaching a new thread (watch UI to see how this works)
-(void)bigTaskAction{
    [self.myActivityIndicator startAnimating];

    [NSThread detachNewThreadSelector:@selector(bigTask)
                             toTarget:self
                           withObject:nil];

}

- (void)viewDidLoad{
    [super viewDidLoad];

    //Create button
    self.myButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    self.myButton.frame = CGRectMake(20, 403, 280, 37);
    [self.myButton addTarget:self
                      action:@selector(bigTaskAction)
            forControlEvents:UIControlEventTouchUpInside];
    [self.myButton setTitle:@"Do Long Task"
                   forState:UIControlStateNormal];

    [self.view addSubview:self.myButton];

    //Create activity indicator
    self.myActivityIndicator = [[UIActivityIndicatorView alloc] init];
    self.myActivityIndicator.frame = CGRectMake(142, 211, 37, 37);
    self.myActivityIndicator.activityIndicatorViewStyle = Image
UIActivityIndicatorViewStyleWhiteLarge;
    self.myActivityIndicator.hidesWhenStopped = NO;

    [self.view addSubview:self.myActivityIndicator];

}

@end

Usage

To use this code, start by setting up a Single View based application in Xcode, like you did in Recipe 1.12. Next, add the code from Listing 6-4 to your own ViewController class.

Run the application. In the iOS Simulator you should see an app with one button and a white activity indicator in the middle of the view. Touch the button and examine the log. You should see the for loop executing and the activity indicator spinning in the iOS app view. When bigTask is complete, the activity indicator will stop spinning.

To compare what would happen without using a new thread, comment out bigTaskAction from Listing 6-4 and then comment in the alternate bigTask method. See the comments in Listing 6-4 if you are unsure of what method to choose.

Run the application with the alternate bigTaskAction and observe how the main thread gets locked up. You will not be able to use the interface, but the for loop will continue to execute and you will see the results in the log.

6.2 Communicating Between the Main Thread and a Background Thread

Problem

When you attempt to update the user interface from a background thread, the changes don’t occur until the background thread is finished processing, which makes components like progress bars useless. You would like to update your user on the progress of background tasks.

Solution

Use the NSObject method performSelectorOnMainThread:withObject:waitUntilDone: to execute a method on the main thread. You will need to put the code that updates your user interface (or otherwise works with the main thread) in its own method.

How It Works

This recipe continues the work you started in Recipe 6.1. However, you are going to add a UIProgressView object property to your application.

UIProgressView is a UIKit class that you can use to create a user interface element that presents the progress of a task from 0% to 100% and looks like a blue bar that moves across the screen. You are going to use the progress view to show your users how much of the bigTask has been completed.

Here is where you will put the UIProgressView object in your ViewController class. This property belongs in the header file.

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@property(strong) UIButton *myButton;
@property(strong) UIActivityIndicatorView *myActivityIndicator;
@property(strong) UIProgressView *myProgressView;

@end

The property myProgressView must also be added to the @synthesize statement in the ViewController implementation file.

#import "ViewController.h"

@implementation ViewController
@synthesize myButton, myActivityIndicator, myProgressView;

...

@end

Note that the entire ViewController implementation is not reproduced here. Take a look at Listing 6-7 to see a complete listing of the code in ViewController in context.

Now, create a method that will update the progress view. This method must be separate from the method where you put the code for the background thread, which is bigTask in this example application.

Name your method updateProgressViewWithPercentage:. The parameter is an NSNumber object indicating how much of the task is complete.

-(void)updateProgressViewWithPercentage:(NSNumber *)percentDone{

}

Make sure to add this method to your ViewController implementation but before the bigTask method. Alternatively, you can add updateProgressViewWithPercentage: to the ViewController header file as a forward declaration if you’d rather locate the actual method after bigTask.

Next, add the code to update the progress view to the new method. You only need to send one message here to the progress view with the updated information.

-(void)updateProgressViewWithPercentage:(NSNumber *)percentDone{
    [self.myProgressView setProgress:[percentDone floatValue]
                            animated:YES];
}

As you can see, you are setting the state of the progress view to the percentage that you are getting from the percentDone parameter. setProgress is actually expecting a primitive float type, which means that you must use the NSNumber floatValue method to return the float value version of the NSNumber object. The second parameter allows you to control whether the progress view will update with animation.

This is the method that you will be calling from your background thread. As in Recipe 6.1, you have a method called bigTask where the background thread code is located. There is a slight change in this code: the number to count to is reduced to 10,000 from 40,000 because counting to 40,000 just took too long while testing this code. Here is the updated bigTask:

-(void) bigTask{
    @autoreleasepool {
        for(int i=0;i<10000;i++){
            NSString *newString = [NSString stringWithFormat:@"i = %i", i];
            NSLog(@"%@", newString);
        }
        [self.myActivityIndicator stopAnimating];
    }
}

Again, the code for bigTask is located in the ViewController implementation. Before you instruct the user interface to update back on the main thread, you need a few things in place. Since you don’t want the interface updating for every single action in the thread, here are some rules to determine when the interface is updated.

You want the interface to update as you reach every 10% of the total task completion time. Since you are counting to 10,000, you can simply update the interface every 1,000 counts. You’ll need an integer to help keep track of this, so add it now and assign the initial value to 1000.

-(void) bigTask{
    @autoreleasepool {
        int updateUIWhen = 1000;
        for(int i=0;i<10000;i++){
            NSString *newString = [NSString stringWithFormat:@"i = %i", i];
            NSLog(@"%@", newString);
        }
        [self.myActivityIndicator stopAnimating];
    }
}

Next, you need to test when your count has reached the value in updateUIWhen. You also need to increment updateUIWhen once you do reach 1,000. Here is one way to do that:

-(void) bigTask{
    @autoreleasepool {
        int updateUIWhen = 1000;
        for(int i=0;i<10000;i++){
            NSString *newString = [NSString stringWithFormat:@"i = %i", i];
            NSLog(@"%@", newString);
            if(i == updateUIWhen){
                updateUIWhen = updateUIWhen + 1000;
            }
        }
        [self.myActivityIndicator stopAnimating];
    }
}

Now you can figure out the percentage complete by dividing i by 10,000. Put that line of code at the first spot after the opening of the if statement.

-(void) bigTask{
    @autoreleasepool {
        int updateUIWhen = 1000;
        for(int i=0;i<10000;i++){
            NSString *newString = [NSString stringWithFormat:@"i = %i", i];
            NSLog(@"%@", newString);
            if(i == updateUIWhen){
                float f = (float)i/10000;

                updateUIWhen = updateUIWhen + 1000;
            }
        }
        [self.myActivityIndicator stopAnimating];
    }
}

If you look closely at the new line of code, you will see that i has (float) in front of it. This is called a type cast. In this case, you are treating i (which is an integer) as a float type so that you can assign the division result to a float type. You need a float type because your progress view is going to need a value between 0 and 1.

Next, create an NSNumber object because that is what the method that will execute on the main thread will need as a parameter.

-(void) bigTask{
    @autoreleasepool {
        int updateUIWhen = 1000;
        for(int i=0;i<10000;i++){
            NSString *newString = [NSString stringWithFormat:@"i = %i", i];
            NSLog(@"%@", newString);
            if(i == updateUIWhen){
                float f = (float)i/10000;
                NSNumber *percentDone = [NSNumber numberWithFloat:f];

                updateUIWhen = updateUIWhen + 1000;
            }
        }
        [self.myActivityIndicator stopAnimating];
    }
}

You now have everything you need to send your message to the user interface back on the main thread. To send a message to the main thread, you can use the NSObject method performSelectorOnMainThread:withObject:waitUntilDone:. You need a method to pass with the @selector keyword, an object that will serve as a parameter to the method (the NSNumber you just created) and a BOOL indicating whether you want to block the current thread until the method has completed.

This code will send that message:

-(void) bigTask{
    @autoreleasepool {
        int updateUIWhen = 1000;
        for(int i=0;i<10000;i++){
            NSString *newString = [NSString stringWithFormat:@"i = %i", i];
            NSLog(@"%@", newString);
            if(i == updateUIWhen){
                float f = (float)i/10000;
                NSNumber *percentDone = [NSNumber numberWithFloat:f];
                [self performSelectorOnMainThread: Image
                      @selector(updateProgressViewWithPercentage:)
                                      withObject:percentDone
                                   waitUntilDone:YES];
                updateUIWhen = updateUIWhen + 1000;
            }
        }
        [self.myActivityIndicator stopAnimating];
    }
}

Here you are sending a message to the main thread where the user interface is executing with a parameter that has information about how much of the task is complete. All the code needed to update the progress view is located in the updateProgressViewWithPercentage: method.

Next, you need to send yet another message to the main thread after the task is complete just to make sure the progress view is completely filled up when you are done. This message looks similar to what you just did.

-(void) bigTask{
    @autoreleasepool {
        int updateUIWhen = 1000;
        for(int i=0;i<10000;i++){
            NSString *newString = [NSString stringWithFormat:@"i = %i", i];
            NSLog(@"%@", newString);
            if(i == updateUIWhen){
                float f = (float)i/10000;
                NSNumber *percentDone = [NSNumber numberWithFloat:f];
                [self performSelectorOnMainThread: Image
                      @selector(updateProgressViewWithPercentage:)
                                      withObject:percentDone
                                   waitUntilDone:YES];
                updateUIWhen = updateUIWhen + 1000;
            }
        }
        [self performSelectorOnMainThread:@selector(updateProgressViewWithPercentage:)
                               withObject:[NSNumber numberWithFloat:1.0]
                            waitUntilDone:YES];
        [self.myActivityIndicator stopAnimating];
    }
}

See Listings 6-5 through 6-8 for the code.

The Code

Listing 6-5. AppDelegate.h

#import <UIKit/UIKit.h>

@class ViewController;

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@property (strong, nonatomic) ViewController *viewController;

@end

Listing 6-6. AppDelegate.m

#import "AppDelegate.h"

#import "ViewController.h"

@implementation AppDelegate

@synthesize window = _window;
@synthesize viewController = _viewController;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:Image
(NSDictionary *)launchOptions{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.viewController = [[ViewController alloc] initWithNibName:@"ViewController"
                                                           bundle:nil];
    self.window.rootViewController = self.viewController;
    [self.window makeKeyAndVisible];

    return YES;
}

@end

Listing 6-7. ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@property(strong) UIButton *myButton;
@property(strong) UIActivityIndicatorView *myActivityIndicator;
@property(strong) UIProgressView *myProgressView;

@end

Listing 6-8. ViewController.m

#import "ViewController.h"

@implementation ViewController
@synthesize myButton, myActivityIndicator, myProgressView;

-(void)updateProgressViewWithPercentage:(NSNumber *)percentDone{
       [self.myProgressView setProgress:[percentDone floatValue]
                               animated:YES];
}

-(void) bigTask{
    @autoreleasepool {
        int updateUIWhen = 1000;
        for(int i=0;i<10000;i++){
            NSString *newString = [NSString stringWithFormat:@"i = %i", i];
            NSLog(@"%@", newString);
            if(i == updateUIWhen){
                float f = (float)i/10000;
                NSNumber *percentDone = [NSNumber numberWithFloat:f];
                [self performSelectorOnMainThread:  Image
                      @selector(updateProgressViewWithPercentage:)
                                      withObject:percentDone
                                  waitUntilDone:YES];
                updateUIWhen = updateUIWhen + 1000;
            }
        }
        [self performSelectorOnMainThread:@selector(updateProgressViewWithPercentage:)
                               withObject:[NSNumber numberWithFloat:1.0]
                            waitUntilDone:YES];
        [self.myActivityIndicator stopAnimating];
    }
}

//do task by detaching a new thread (watch UI to see how this works)
-(void)bigTaskAction{
    [self.myActivityIndicator startAnimating];

    [NSThread detachNewThreadSelector:@selector(bigTask)
                             toTarget:self
                           withObject:nil];

}

- (void)viewDidLoad{
    [super viewDidLoad];

        //Create button
        self.myButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
        self.myButton.frame = CGRectMake(20, 403, 280, 37);
        [self.myButton addTarget:self
                          action:@selector(bigTaskAction)
                forControlEvents:UIControlEventTouchUpInside];
        [self.myButton setTitle:@"Do Long Task"
                       forState:UIControlStateNormal];
    [self.view addSubview:self.myButton];

    //Create activity indicator
    self.myActivityIndicator = [[UIActivityIndicatorView alloc] init];
    self.myActivityIndicator.frame = CGRectMake(142, 211, 37, 37);
    self.myActivityIndicator.activityIndicatorViewStyle = Image
UIActivityIndicatorViewStyleWhiteLarge;
    self.myActivityIndicator.hidesWhenStopped = NO;
    [self.view addSubview:self.myActivityIndicator];

    //Create label
    self.myProgressView = [[UIProgressView alloc] init];
    self.myProgressView.frame = CGRectMake(20, 20, 280, 9);
    [self.view addSubview:self.myProgressView];

}

@end

Usage

To use this code, start by setting up a Single View based application in Xcode, like you did in Recipe 1.12. Next, add the code from Listing 6-8tk to your own ViewController class.

Run the application. In the iOS Simulator you should see an app with one button and an empty progress view. You will also see a white activity indicator in the middle of the view. Touch the button to start bigTask in the background thread and examine the log. As bigTask runs, you should see the progress view filling up in 10% increments until the task is complete.

6.3 Locking Threads with NSLock

Problem

Your application uses multiple threads, but at times you need to make sure two threads are not attempting to use the same block of code. Your application could cause conflicts that may result in user confusion or files being accessed too many times.

For example, try to run the application from Recipe 6.2 but touch the button a second time after the progress view starts to fill from the first task. If you look closely, the progress view will start to jump back and forth as each thread changes the progress view’s value to the current percentage for that particular thread.

Solution

Use NSLock to make other threads wait until the thread is done processing for key blocks of code.

How It Works

For this example, let’s assume that you are starting with the application from Recipe 6.2 and your app behaves as described in the Problem section. What you want to do is make sure that bigTask only executes in one thread at a time. This will make each thread wait its turn.

The first thing you need to do is add an NSLock object to your view controller. The following is an example of setting this up as a property:

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@property(strong) UIButton *myButton;
@property(strong) UIActivityIndicatorView *myActivityIndicator;
@property(strong) UIProgressView *myProgressView;
@property(strong) NSLock *threadLock;

@end

This property must also be included with the @synthesize statement in the view controller’s implementation.

#import "ViewController.h"

@implementation ViewController
@synthesize myButton, myActivityIndicator, myProgressView, threadLock;

@end

NOTE: It is not absolutely necessary to include objects like this as properties. You may also simply add them as a local instance in your view controller implementation. This is a design decision that you must make. I included NSLock like this to stay as consistent as possible with the earlier recipes.

Before you use NSLock, you need to instantiate an object and assign this to the property that you have in place. The best place to do this in a view controller is in the viewDidLoad method.

#import "ViewController.h"

@implementation ViewController
@synthesize myButton, myActivityIndicator, myProgressView, threadLock;

- (void)viewDidLoad{
    [super viewDidLoad];

    //Create the NSLock object
    self.threadLock = [[NSLock alloc] init];
}

@end

All the code for the view controller is not listed here, but you can see it in Listing 6-11.

Now that you have an NSLock object, all you need to do is lock down your thread. To do this, use the lock message to the NSLock object at the beginning of the thread’s code and an unlock message at the end of the thread’s code.

This code goes in your bigTask method, which is located in the view controller’s implementation.

-(void) bigTask{
    [self.threadLock lock];
    @autoreleasepool {
        int updateUIWhen = 1000;
        for(int i=0;i<10000;i++){
            NSString *newString = [NSString stringWithFormat:@"i = %i", i];
            NSLog(@"%@", newString);
            if(i == updateUIWhen){
                float f = (float)i/10000;
                NSNumber *percentDone = [NSNumber numberWithFloat:f];
                [self performSelectorOnMainThread:  Image
                      @selector(updateProgressViewWithPercentage:)
                                      withObject:percentDone
                                  waitUntilDone:YES];
                updateUIWhen = updateUIWhen + 1000;
            }
        }
        [self performSelectorOnMainThread:@selector(updateProgressViewWithPercentage:)
                               withObject:[NSNumber numberWithFloat:1.0]
                            waitUntilDone:YES];
        [self.myActivityIndicator stopAnimating];
    }
    [self.threadLock unlock];
}

Now you can be sure that this thread will be locked until the thread is done executing. See Listings 6-9 through 6-12 for the code.

The Code

Listing 6-9. AppDelegate.h

#import <UIKit/UIKit.h>

@class ViewController;

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@property (strong, nonatomic) ViewController *viewController;

@end

Listing 6-10. AppDelegate.m

#import "AppDelegate.h"

#import "ViewController.h"

@implementation AppDelegate

@synthesize window = _window;
@synthesize viewController = _viewController;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:  Image
(NSDictionary *)launchOptions{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.viewController = [[ViewController alloc] initWithNibName:@"ViewController"
                                                           bundle:nil];
    self.window.rootViewController = self.viewController;
    [self.window makeKeyAndVisible];

    return YES;
}

@end

Listing 6-11. ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@property(strong) UIButton *myButton;
@property(strong) UIActivityIndicatorView *myActivityIndicator;
@property(strong) UIProgressView *myProgressView;
@property(strong) NSLock *threadLock;

@end

Listing 6-12. ViewController.m

#import "ViewController.h"

@implementation ViewController
@synthesize myButton, myActivityIndicator, myProgressView, threadLock;

-(void)updateProgressViewWithPercentage:(NSNumber *)percentDone{
       [self.myProgressView setProgress:[percentDone floatValue]
                               animated:YES];
}

-(void) bigTask{
    [self.threadLock lock];
    @autoreleasepool {
        int updateUIWhen = 1000;
        for(int i=0;i<10000;i++){
            NSString *newString = [NSString stringWithFormat:@"i = %i", i];
            NSLog(@"%@", newString);
            if(i == updateUIWhen){
                float f = (float)i/10000;
                NSNumber *percentDone = [NSNumber numberWithFloat:f];
                [self performSelectorOnMainThread:  Image
                      @selector(updateProgressViewWithPercentage:)
                                      withObject:percentDone
                                  waitUntilDone:YES];
                updateUIWhen = updateUIWhen + 1000;
            }
        }
        [self performSelectorOnMainThread:@selector(updateProgressViewWithPercentage:)
                               withObject:[NSNumber numberWithFloat:1.0]
                            waitUntilDone:YES];
        [self.myActivityIndicator stopAnimating];
    }
    [self.threadLock unlock];

}

//do task by detaching a new thread (watch UI to see how this works)
-(void)bigTaskAction{
    [self.myActivityIndicator startAnimating];

    [NSThread detachNewThreadSelector:@selector(bigTask)
                             toTarget:self
                           withObject:nil];

}

- (void)viewDidLoad{
    [super viewDidLoad];

    //Create the NSLock object
    self.threadLock = [[NSLock alloc] init];

    //Create button
    self.myButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    self.myButton.frame = CGRectMake(20, 403, 280, 37);
    [self.myButton addTarget:self
                      action:@selector(bigTaskAction)
            forControlEvents:UIControlEventTouchUpInside];
     [self.myButton setTitle:@"Do Long Task"
                   forState:UIControlStateNormal];
    [self.view addSubview:self.myButton];

    //Create activity indicator
    self.myActivityIndicator = [[UIActivityIndicatorView alloc] init];
    self.myActivityIndicator.frame = CGRectMake(142, 211, 37, 37);
    self.myActivityIndicator.activityIndicatorViewStyle = Image
UIActivityIndicatorViewStyleWhiteLarge;
    self.myActivityIndicator.hidesWhenStopped = NO;
    [self.view addSubview:self.myActivityIndicator];

    //Create label
    self.myProgressView = [[UIProgressView alloc] init];
    self.myProgressView.frame = CGRectMake(20, 20, 280, 9);
    [self.view addSubview:self.myProgressView];

}

@end

Usage

To test this code, start by setting up a Single View based application in Xcode, like you did in Recipe 1.12. Next, add the code from Listing 6-12 to your own ViewController class.

Run the application. In the iOS Simulator you should see an app with one button and an empty progress view. You will also see a white activity indicator in the middle of the view. Touch the button two times to start bigTask running in two background threads.

As bigTask runs, you should see the progress view filling up in 10% increments until the task is complete. Observe that the progress view fills up to 100% before going back to 0% and progressing again to 100%. NSLock is doing what it’s supposed to do.

6.4 Locking Threads with @synchronized

Problem

Your application uses multiple threads, but at times you need to make sure two threads are not attempting to use the same block of code, and you would like an alternative to NSLock.

NOTE: @synchronized and NSLock solve the same problem with threads, so this recipe will look very similar to Recipe 6.3. These two approaches, while similar, are implemented differently to allow @synchronized the ability to handle exceptions. This also causes @synchronized to have  more of a performance hit than NSLock.

Use the application from Recipe 6.2 as a starting point. The application from Recipe 6.2 will execute the thread as many times as you touch the button, causing the progress view to behave unexpectedly.

Solution

To make sure that only one thread may use a block of code at a time, enclose the entire block of code in curly braces preceded by the @synchronized directive.

How It Works

For this example, let’s assume that you are starting with the application from Recipe 6.2 and that your app behaves as described in the Problem section. You want to make sure that bigTask only executes in one thread at a time. This will make each thread wait its turn.

All the code for the view controller is not listed here, but you can see it in Listing 6-15.

Unlike Recipe 6.3, you will not need to add any property code. All you need to do is enclose the code in the bigTask in curly braces with the @synchronized directive.

-(void) bigTask{
    @synchronized(self){
        @autoreleasepool {
            int updateUIWhen = 1000;
            for(int i=0;i<10000;i++){
                NSString *newString = [NSString stringWithFormat:@"i = %i", i];
                NSLog(@"%@", newString);
                if(i == updateUIWhen){
                    float f = (float)i/10000;
                    NSNumber *percentDone = [NSNumber numberWithFloat:f];
                    [self performSelectorOnMainThread:  Image
                          @selector(updateProgressViewWithPercentage:)
                                           withObject:percentDone
                                        waitUntilDone:YES];
                    updateUIWhen = updateUIWhen + 1000;
                }
            }
            [self performSelectorOnMainThread:  Image
                  @selector(updateProgressViewWithPercentage:)
                                   withObject:[NSNumber numberWithFloat:1.0]
                                waitUntilDone:YES];
            [self.myActivityIndicator stopAnimating];
        }
    }
}

See Listings 6-13 through 6-16 for the code.

The Code

Listing 6-13. AppDelegate.h

#import <UIKit/UIKit.h>

@class ViewController;

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@property (strong, nonatomic) ViewController *viewController;

@end

Listing 6-14. AppDelegate.m

#import "AppDelegate.h"

#import "ViewController.h"

@implementation AppDelegate

@synthesize window = _window;
@synthesize viewController = _viewController;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:  Image
(NSDictionary *)launchOptions{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.viewController = [[ViewController alloc] initWithNibName:@"ViewController"
                                                           bundle:nil];
    self.window.rootViewController = self.viewController;
    [self.window makeKeyAndVisible];

    return YES;
}

@end

Listing 6-15. ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@property(strong) UIButton *myButton;
@property(strong) UIActivityIndicatorView *myActivityIndicator;
@property(strong) UIProgressView *myProgressView;

@end

Listing 6-16. ViewController.m

#import "ViewController.h"

@implementation ViewController
@synthesize myButton, myActivityIndicator, myProgressView;

-(void)updateProgressViewWithPercentage:(NSNumber *)percentDone{
       [self.myProgressView setProgress:[percentDone floatValue]
                               animated:YES];
}

-(void) bigTask{
    @synchronized(self){
        @autoreleasepool {
            int updateUIWhen = 1000;
            for(int i=0;i<10000;i++){
                NSString *newString = [NSString stringWithFormat:@"i = %i", i];
                NSLog(@"%@", newString);
                if(i == updateUIWhen){
                    float f = (float)i/10000;
                    NSNumber *percentDone = [NSNumber numberWithFloat:f];
                    [self performSelectorOnMainThread:  Image
                          @selector(updateProgressViewWithPercentage:)
                                           withObject:percentDone
                                        waitUntilDone:YES];
                    updateUIWhen = updateUIWhen + 1000;
                }
            }
            [self performSelectorOnMainThread:  Image
                  @selector(updateProgressViewWithPercentage:)
                                   withObject:[NSNumber numberWithFloat:1.0]
                                waitUntilDone:YES];
            [self.myActivityIndicator stopAnimating];
        }
    }
}

//do task by detaching a new thread (watch UI to see how this works)
-(void)bigTaskAction{
    [self.myActivityIndicator startAnimating];

    [NSThread detachNewThreadSelector:@selector(bigTask)
                             toTarget:self
                           withObject:nil];

}

- (void)viewDidLoad{
    [super viewDidLoad];

    //Create button
    self.myButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    self.myButton.frame = CGRectMake(20, 403, 280, 37);
    [self.myButton addTarget:self
                      action:@selector(bigTaskAction)
            forControlEvents:UIControlEventTouchUpInside];
    [self.myButton setTitle:@"Do Long Task"
                   forState:UIControlStateNormal];
    [self.view addSubview:self.myButton];

    //Create activity indicator
    self.myActivityIndicator = [[UIActivityIndicatorView alloc] init];
    self.myActivityIndicator.frame = CGRectMake(142, 211, 37, 37);
    self.myActivityIndicator.activityIndicatorViewStyle = Image
UIActivityIndicatorViewStyleWhiteLarge;
    self.myActivityIndicator.hidesWhenStopped = NO;
    [self.view addSubview:self.myActivityIndicator];

    //Create label
    self.myProgressView = [[UIProgressView alloc] init];
    self.myProgressView.frame = CGRectMake(20, 20, 280, 9);
    [self.view addSubview:self.myProgressView];

}

@end

Usage

To test this code, start by setting up a Single View based application in Xcode, like you did in Recipe 1.12. Next, add the code from Listing 6-16 to your own ViewController class.

Run the application. In the iOS Simulator you should see an app with one button and an empty progress view at the top of the view. You will also see a white activity indicator in the middle of the view. Touch the button two times to start bigTask running in two background threads.

As bigTask runs, you should see the progress view filling up in 10% increments until the task is complete. Observe that the progress view fills up to 100% before going back to 0% and progressing again to 100%. @synchronized is doing what it’s supposed to do.

6.5 Asynchronous Processing with Grand Central Dispatch (GCD)

Problem

You want to implement asynchronous processing in your application, you plan on supporting your application on newer OSX and iOS systems, and you would rather not use NSThread with the various locking mechanisms required to make your app thread safe.

NOTE: Applications that use multiple threads can become more complicated and sometimes slower because developers need to worry about situations where regions of code, resources, or data structures may be accessed by more than one thread at the same time. Keeping threads locked for short periods of time (as in Recipes 6.3 and 6.4) helps to make code thread safe (safe for use by multiple threads). However, this can prevent applications from making full use of the available resources.

Solution

Consider using Grand Central Dispatch (GCD) as an alternative to NSThread if you know your users have updated systems (or if you choose to support updated systems only). GCD solves the same problems as NSThread and follows the same basic idea of executing code asynchronously. GCD is a newer technology that is more efficient in computers that have multiple processors. GCD was introduced for OSX in version 10.6 and iOS in version 4.

You don’t need to do anything special to add GCD support to your application if you are developing with a version of OSX that supports GCD. GCD does require the use of a programming technique called blocks, which can take some getting used to.

Blocks are regions of code that are treated like objects. This means that you can put lines of code between curly brackets and then treat them like an object. Usually, you will see blocks used as a parameter to a method, and that is how blocks are used in GCD. You will see how blocks are used with GCD in the following section.

How It Works

For this recipe, you are going to solve the same problem as you did in Recipes 6.1, 6.2, and 6.3. Essentially, you have an iOS app with a button that sets off a long task and you want to keep the user interface responsive and let the progress view fill up as the task executes. This time you will use GCD to fix this problem.

GCD uses blocks instead of methods (with the @selector directive). This means that you don’t need to put all the code that you want to execute into a new method. Instead, you will pass the code into a GCD function as a parameter right from the action method bigTaskAction. Use the GCD function dispatch_async to do this.

-(void)bigTaskAction{
    [self.myActivityIndicator startAnimating];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        int updateUIWhen = 1000;
        for(int i=0;i<10000;i++){
            NSString *newString = [NSString stringWithFormat:@"i = %i", i];
            NSLog(@"%@", newString);
            if(i == updateUIWhen){
                float f = (float)i/10000;
                NSNumber *percentDone = [NSNumber numberWithFloat:f];

                updateUIWhen = updateUIWhen + 1000;
            }
        }


    });
}

Let’s look at the first line of code with that GCD function.

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

The first part is the name of the function is dispatch_async, which is a GCD function that executes asynchronously. There is also a similar function that executes code synchronously called dispatch_sync. The first parameter that the function requires is a dispatch queue.

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

You are supplying another function that in turn returns the default dispatch queue for this application.

NOTE: GCD has a concept of code queues that are scheduled to run on the next available processor. When you use GCD you need to specify what queue that you want your code to be put into. You are using the default queue here, which you can use for background processing too. You can also use the main queue, which is like the main thread for the user interface.

The next parameter in this function is the code block. You know that you are working with a code block because it starts off with the ^ symbol and has a beginning curly bracket.

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

All the lines of code that come after are part of the block parameter. This code is scheduled to execute when the next processor is available. The entire GCD function ends with the code );.

So far, all you are doing is scheduling this big task to execute in the background. But, you still want to update the user interface as the task progresses. Instead of performing a selector on the main thread, you can use another GCD function to update the user interface on the main thread. This task should be done synchronously, so use the GCD function along with a main dispatch queue.

dispatch_sync(dispatch_get_main_queue(), ^{
    [self.myProgressView setProgress:[percentDone floatValue]
                            animated:YES];

});

This takes the place of the method you had to code before. You can just use the variables on hand without worrying about passing parameters, as you can see when you put this GCD call into the context of the entire code block.

-(void)bigTaskAction{
    [self.myActivityIndicator startAnimating];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        int updateUIWhen = 1000;
        for(int i=0;i<10000;i++){
            NSString *newString = [NSString stringWithFormat:@"i = %i", i];
            NSLog(@"%@", newString);
            if(i == updateUIWhen){
                float f = (float)i/10000;
                NSNumber *percentDone = [NSNumber numberWithFloat:f];

                dispatch_sync(dispatch_get_main_queue(), ^{
                    [self.myProgressView setProgress:[percentDone floatValue]
                                            animated:YES];

                });

                updateUIWhen = updateUIWhen + 1000;
            }
        }


    });
}

NOTE: With GCD dispatch queues you don’t know for sure what order code blocks will execute when you use dispatch_async. The system picks the most efficient way. So, if order is important (like when you are updating your interface), use dispatch_sync.

Finally, just to be complete, you want to finish filling the progress view when the task is complete and stop the activity indicator. Use GCD again to do this by scheduling another task for the main queue at the end of the block of code.

-(void)bigTaskAction{
    [self.myActivityIndicator startAnimating];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        int updateUIWhen = 1000;
        for(int i=0;i<10000;i++){
            NSString *newString = [NSString stringWithFormat:@"i = %i", i];
            NSLog(@"%@", newString);
            if(i == updateUIWhen){
                float f = (float)i/10000;
                NSNumber *percentDone = [NSNumber numberWithFloat:f];

                dispatch_sync(dispatch_get_main_queue(), ^{
                    [self.myProgressView setProgress:[percentDone floatValue]
                                            animated:YES];

                });

                updateUIWhen = updateUIWhen + 1000;
            }
        }

        dispatch_sync(dispatch_get_main_queue(), ^{

            [self.myProgressView setProgress:1.0
                                    animated:YES];
            [self.myActivityIndicator stopAnimating];

        });

    });
}

See Listings 6-17 through 6-20 for the code.

The Code

Listing 6-17. AppDelegate.h

#import <UIKit/UIKit.h>

@class ViewController;

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@property (strong, nonatomic) ViewController *viewController;

@end

Listing 6-18. AppDelegate.m

#import "AppDelegate.h"

#import "ViewController.h"

@implementation AppDelegate

@synthesize window = _window;
@synthesize viewController = _viewController;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:  Image
(NSDictionary *)launchOptions{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.viewController = [[ViewController alloc] initWithNibName:@"ViewController"
                                                           bundle:nil];
    self.window.rootViewController = self.viewController;
    [self.window makeKeyAndVisible];

    return YES;
}

@end

Listing 6-19. ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@property(strong) UIButton *myButton;
@property(strong) UIActivityIndicatorView *myActivityIndicator;
@property(strong) UIProgressView *myProgressView;

@end

Listing 6-20. ViewController.m

#import "ViewController.h"

@implementation ViewController
@synthesize myButton, myActivityIndicator, myProgressView;

-(void)bigTaskAction{
    [self.myActivityIndicator startAnimating];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        int updateUIWhen = 1000;
        for(int i=0;i<10000;i++){
            NSString *newString = [NSString stringWithFormat:@"i = %i", i];
            NSLog(@"%@", newString);
            if(i == updateUIWhen){
                float f = (float)i/10000;
                NSNumber *percentDone = [NSNumber numberWithFloat:f];

                dispatch_sync(dispatch_get_main_queue(), ^{
                    [self.myProgressView setProgress:[percentDone floatValue]
                                            animated:YES];

                });

                updateUIWhen = updateUIWhen + 1000;
            }
        }

        dispatch_sync(dispatch_get_main_queue(), ^{

            [self.myProgressView setProgress:1.0
                                    animated:YES];
            [self.myActivityIndicator stopAnimating];

        });

    });
}

- (void)viewDidLoad{
    [super viewDidLoad];

    //Create button
    self.myButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    self.myButton.frame = CGRectMake(20, 403, 280, 37);
    [self.myButton addTarget:self
                      action:@selector(bigTaskAction)
            forControlEvents:UIControlEventTouchUpInside];
    [self.myButton setTitle:@"Do Long Task"
                   forState:UIControlStateNormal];
    [self.view addSubview:self.myButton];

    //Create activity indicator
    self.myActivityIndicator = [[UIActivityIndicatorView alloc] init];
    self.myActivityIndicator.frame = CGRectMake(142, 211, 37, 37);
    self.myActivityIndicator.activityIndicatorViewStyle = Image
UIActivityIndicatorViewStyleWhiteLarge;
    self.myActivityIndicator.hidesWhenStopped = NO;
    [self.view addSubview:self.myActivityIndicator];

    //Create label
    self.myProgressView = [[UIProgressView alloc] init];
    self.myProgressView.frame = CGRectMake(20, 20, 280, 9);
    [self.view addSubview:self.myProgressView];

}

@end

Usage

To test this code, start by setting up a Single View based application in Xcode, like you did in Recipe 1.12. Next, add the code from Listing 6-20tk to your own ViewController class.

Run the application. In the iOS Simulator you should see an app with one button and an empty progress view. You will also see a white activity indicator in the middle of the view. Touch the button to start the bigTask running. As bigTask runs, you should see the progress view filling up in 10% increments until the task is complete.

Generally speaking, GCD is the preferred way to do background processing. If you are targeting newer systems, GCD should be your first choice when deciding what technology to implement. GCD has been optimized for multi-core applications so you will see a large boast in your application’s performance when using GCD on multi-core Macs.

GCD is simpler to use as compared to NSThread since there is no need for an additional object nor is there a need to code an additional method as you would for NSThread. However, you will see plenty of examples of NSThread to do background processing, and that option is available to you.

6.6 Using Serial Queues in GCD

Problem

You use GCD to perform asynchronous processing and you have a situation where you require blocks to execute one at a time in the order in which they are encountered in code. For example, in Recipe 6.5 you run into the same problem as you did earlier in this chapter when users touch the button after the long task is running (the progress view bounces back and forth).

Previously, you solved this problem using NSLock or @synchronized() but these come at a cost, which negates some of the benefits of using GCD in the first place.

Solution

Instead of locking the code, use a GCD serial queue to load up the code blocks to be executed in the order in which the code blocks were placed in the queue. You can use the GCD function dispatch_queue_create(DISPATCH_QUEUE_SERIAL, 0) to create a serial queue. Make sure that the serial queue stays in scope for the lifetime of the object it serves.

How It Works

For the purposes of this recipe, you’ll alter Recipe 6.5 to use a serial queue to fix the problem that you run into. The first thing you need is a property for the serial queue.

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@property(strong) UIButton *myButton;
@property(strong) UIActivityIndicatorView *myActivityIndicator;
@property(strong) UIProgressView *myProgressView;
@property(assign) dispatch_queue_t serialQueue;

@end

This could also be a local instance in the view controller, as long as the queue stays in scope as long as needed.

You need to make sure that the serial queue is implemented in the view controller’s @synthesize statement as well.

#import "ViewController.h"

@implementation ViewController
@synthesize myButton, myActivityIndicator, myProgressView, serialQueue;

@end

The viewDidLoad view controller method is a great place to locate the code required to create the serial queue.

#import "ViewController.h"

@implementation ViewController
@synthesize myButton, myActivityIndicator, myProgressView, serialQueue;

...

- (void)viewDidLoad{
    [super viewDidLoad];

    self.serialQueue = dispatch_queue_create(DISPATCH_QUEUE_SERIAL, 0);

}

...

@end

This function requires a parameter to specify the type of queue to create. Here you are using DISPATCH_QUEUE_SERIAL because you want a serial queue that ensures that only one block of code executes at a time in the order that each block of code was placed in the queue. Some of the view controller code has been left out; see Listing 6-23 for the entire view controller code.

The next change that you need to make to the Recipe 6.5 code is to replace the default queue used before with the serial queue you just created. This happens in the bigTaskAction method.

#import "ViewController.h"

@implementation ViewController
@synthesize myButton, myActivityIndicator, myProgressView, serialQueue;

...

-(void)bigTaskAction{

    dispatch_async(self.serialQueue, ^{

        dispatch_sync(dispatch_get_main_queue(), ^{
            [self.myActivityIndicator startAnimating];
        });

        int updateUIWhen = 1000;
        for(int i=0;i<10000;i++){
            NSString *newString = [NSString stringWithFormat:@"i = %i", i];
            NSLog(@"%@", newString);
            if(i == updateUIWhen){
                float f = (float)i/10000;
                NSNumber *percentDone = [NSNumber numberWithFloat:f];

                dispatch_sync(dispatch_get_main_queue(), ^{
                    [self.myProgressView setProgress:[percentDone floatValue]
                                            animated:YES];

                });

                updateUIWhen = updateUIWhen + 1000;
            }
        }

        dispatch_sync(dispatch_get_main_queue(), ^{

            [self.myProgressView setProgress:1.0
                                    animated:YES];
            [self.myActivityIndicator stopAnimating];

        });
    });

}

...

@end

As you can see, you moved the message to start animating the activity indicator to be inside the main block for this action. You also put it into the main queue because it involved updating the user interface. The reasoning is that you want the activity indicator to start spinning each time a block like this executes in the serial queue. See Listings 6-21 through 6-24 for the code.

The Code

Listing 6-21. AppDelegate.h

#import <UIKit/UIKit.h>

@class ViewController;

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@property (strong, nonatomic) ViewController *viewController;

@end

Listing 6-22. AppDelegate.m

#import "AppDelegate.h"

#import "ViewController.h"

@implementation AppDelegate

@synthesize window = _window;
@synthesize viewController = _viewController;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:  Image
(NSDictionary *)launchOptions{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.viewController = [[ViewController alloc] initWithNibName:@"ViewController"
                                                           bundle:nil];
    self.window.rootViewController = self.viewController;
    [self.window makeKeyAndVisible];

    return YES;
}

@end

Listing 6-23. ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@property(strong) UIButton *myButton;
@property(strong) UIActivityIndicatorView *myActivityIndicator;
@property(strong) UIProgressView *myProgressView;
@property(assign) dispatch_queue_t serialQueue;

@end

Listing 6-24. ViewController.m

#import "ViewController.h"

@implementation ViewController
@synthesize myButton, myActivityIndicator, myProgressView, serialQueue;

-(void)bigTaskAction{

    dispatch_async(self.serialQueue, ^{

        dispatch_sync(dispatch_get_main_queue(), ^{
            [self.myActivityIndicator startAnimating];
        });

        int updateUIWhen = 1000;
        for(int i=0;i<10000;i++){
            NSString *newString = [NSString stringWithFormat:@"i = %i", i];
            NSLog(@"%@", newString);
            if(i == updateUIWhen){
                float f = (float)i/10000;
                NSNumber *percentDone = [NSNumber numberWithFloat:f];

                dispatch_sync(dispatch_get_main_queue(), ^{
                    [self.myProgressView setProgress:[percentDone floatValue]
                                            animated:YES];

                });

                updateUIWhen = updateUIWhen + 1000;
            }
        }

        dispatch_sync(dispatch_get_main_queue(), ^{

            [self.myProgressView setProgress:1.0
                                    animated:YES];
            [self.myActivityIndicator stopAnimating];

        });
    });

}

- (void)viewDidLoad{
    [super viewDidLoad];

    self.serialQueue = dispatch_queue_create(DISPATCH_QUEUE_SERIAL, 0);

    //Create button
    self.myButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    self.myButton.frame = CGRectMake(20, 403, 280, 37);
    [self.myButton addTarget:self
                      action:@selector(bigTaskAction)
            forControlEvents:UIControlEventTouchUpInside];
    [self.myButton setTitle:@"Do Long Task"
                   forState:UIControlStateNormal];
    [self.view addSubview:self.myButton];

    //Create activity indicator
    self.myActivityIndicator = [[UIActivityIndicatorView alloc] init];
    self.myActivityIndicator.frame = CGRectMake(142, 211, 37, 37);
    self.myActivityIndicator.activityIndicatorViewStyle = Image
UIActivityIndicatorViewStyleWhiteLarge;
    self.myActivityIndicator.hidesWhenStopped = NO;
    [self.view addSubview:self.myActivityIndicator];

    //Create label
    self.myProgressView = [[UIProgressView alloc] init];
    self.myProgressView.frame = CGRectMake(20, 20, 280, 9);
    [self.view addSubview:self.myProgressView];

}

@end

Usage

To test out this code, start by setting up an application like the one in Recipe 6.5.

Run the application. In the iOS Simulator you should see an app with one button and an empty progress view. You will also see a white activity indicator in the middle of the view. Touch the button two times to start bigTask running in two background threads.

As bigTask runs, you should see the progress view filling up in 10% increments until the task is complete. This process will repeat based on the amount of times you touch the button. The progress view should not keep jumping back and forth.

6.7 Implement Asynchronous Processing Using NSOperationQueue

Problem

You would like to add asynchronous processing to your app, but you prefer to use a more object-oriented approach than the GCD approach.

Solution

Use NSOperationQueue if you want to use GCD but would rather not use the GCD library directly.

NOTE: NSOperationQueue is available for iOS 2 and above and OSX 10.5 and above. This makes NSOperationQueue ideal when you want to support applications that run on older systems and you would rather not use NSThread with thread locking. When you use NSOperationQueue, the details of the implementation are hidden from you. Older systems will support NSOperationQueue with threads while newer systems will use GCD.

NSOperationQueue represents a queue of code that will execute. You can use NSOperationQueue to run code in the background or in a main queue for user interface actions.

NSOperationQueue can add code in a few ways. If the OS supports blocks (iOS 4 and above and OSX 10.6 and above), you can just add code directly to a queue using the addOperationWithBlock: method.

If not, you must set up the code that you want to execute as a separate subclass that is a subclass of NSOperation. A subclass of NSOperation will act like a block in that the class will encapsulate data and code that will execute in a queue.

How It Works

For this recipe, you are going to solve the same problem that was presented in Recipe 6.3. But, instead of using threads that you must lock, you will use an operation queue and the main queue to dispatch code asynchronously. Again, start with Recipe 6.2 as a template and change it to use operation queues (see Listings 6-25 through 6-28 for the complete code in context).

First, add local instances for the main queue and a serial queue right in the view controller’s implementation.

#import "ViewController.h"

@implementation ViewController
@synthesize myButton, myActivityIndicator, myProgressView;
NSOperationQueue *serialQueue;
NSOperationQueue *mainQueue;

@end

The main queue is where the user interface gets its instructions. The serial queue executes code blocks one at a time in the order in which they are received, just like the GCD serial queue in Recipe 6.6.

The viewDidLoad method is the ideal place to instantiate these two queue objects.

#import "ViewController.h"

@implementation ViewController
@synthesize myButton, myActivityIndicator, myProgressView;
NSOperationQueue *serialQueue;
NSOperationQueue *mainQueue;

- (void)viewDidLoad{
    [super viewDidLoad];

    //Create the operation queues
    mainQueue = [NSOperationQueue mainQueue];

    serialQueue = [[NSOperationQueue alloc] init];
    serialQueue.maxConcurrentOperationCount = 1;

}

@end

You can just get a reference to the main queue by using the NSOperationQueue mainQueue method. This is a Singleton that always returns an instance of the main queue. You set up the serial queue using the alloc and init constructor. By setting the maxConcurrentOperationCount to one, you are making this a serial queue because it may only do one operation at a time.

Once you have the queues set up, you can use them to schedule your blocks right from the bigTaskAction method.

-(void)bigTaskAction{

    [serialQueue addOperationWithBlock: ^{

        [mainQueue addOperationWithBlock: ^{

            [self.myActivityIndicator startAnimating];

        }];

        int updateUIWhen = 1000;
        for(int i=0;i<10000;i++){
            NSString *newString = [NSString stringWithFormat:@"i = %i", i];
            NSLog(@"%@", newString);
            if(i == updateUIWhen){
                float f = (float)i/10000;
                NSNumber *percentDone = [NSNumber numberWithFloat:f];

                [mainQueue addOperationWithBlock: ^{
                    [self.myProgressView setProgress:[percentDone floatValue]
                                            animated:YES];

                }];

                updateUIWhen = updateUIWhen + 1000;
            }
        }

        [mainQueue addOperationWithBlock: ^{

            [self.myProgressView setProgress:1.0
                                    animated:YES];
            [self.myActivityIndicator stopAnimating];

        }];
    }];
}

If you went through Recipe 6.6, you can see that this is essentially following the same pattern as what you did with GCD. See Listings 6-25 through 6-28 for the code.

The Code

Listing 6-25. AppDelegate.h

#import <UIKit/UIKit.h>

@class ViewController;

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@property (strong, nonatomic) ViewController *viewController;

@end

Listing 6-26. AppDelegate.m

#import "AppDelegate.h"

#import "ViewController.h"

@implementation AppDelegate

@synthesize window = _window;
@synthesize viewController = _viewController;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:  Image
(NSDictionary *)launchOptions{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.viewController = [[ViewController alloc] initWithNibName:@"ViewController"
                                                           bundle:nil];
    self.window.rootViewController = self.viewController;
    [self.window makeKeyAndVisible];

    return YES;
}

@end

Listing 6-27. ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@property(strong) UIButton *myButton;
@property(strong) UIActivityIndicatorView *myActivityIndicator;
@property(strong) UIProgressView *myProgressView;

@end

Listing 6-28. ViewController.m

#import "ViewController.h"

@implementation ViewController
@synthesize myButton, myActivityIndicator, myProgressView;
NSOperationQueue *serialQueue;
NSOperationQueue *mainQueue;

-(void)bigTaskAction{

    [serialQueue addOperationWithBlock: ^{

        [mainQueue addOperationWithBlock: ^{

            [self.myActivityIndicator startAnimating];

        }];

        int updateUIWhen = 1000;
        for(int i=0;i<10000;i++){
            NSString *newString = [NSString stringWithFormat:@"i = %i", i];
            NSLog(@"%@", newString);
            if(i == updateUIWhen){
                float f = (float)i/10000;
                NSNumber *percentDone = [NSNumber numberWithFloat:f];

                [mainQueue addOperationWithBlock: ^{
                    [self.myProgressView setProgress:[percentDone floatValue]
                                            animated:YES];

                }];

                updateUIWhen = updateUIWhen + 1000;
            }
        }

        [mainQueue addOperationWithBlock: ^{

            [self.myProgressView setProgress:1.0
                                    animated:YES];
            [self.myActivityIndicator stopAnimating];

        }];
    }];
}

- (void)viewDidLoad{
    [super viewDidLoad];

    //Create the operation queues
    mainQueue = [NSOperationQueue mainQueue];

    serialQueue = [[NSOperationQueue alloc] init];
    serialQueue.maxConcurrentOperationCount = 1;

    //Create button
    self.myButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    self.myButton.frame = CGRectMake(20, 403, 280, 37);
    [self.myButton addTarget:self
                      action:@selector(bigTaskAction)
            forControlEvents:UIControlEventTouchUpInside];
    [self.myButton setTitle:@"Do Long Task"
                   forState:UIControlStateNormal];
    [self.view addSubview:self.myButton];

    //Create activity indicator
    self.myActivityIndicator = [[UIActivityIndicatorView alloc] init];
    self.myActivityIndicator.frame = CGRectMake(142, 211, 37, 37);
    self.myActivityIndicator.activityIndicatorViewStyle = Image
UIActivityIndicatorViewStyleWhiteLarge;
    self.myActivityIndicator.hidesWhenStopped = NO;
    [self.view addSubview:self.myActivityIndicator];

    //Create label
    self.myProgressView = [[UIProgressView alloc] init];
    self.myProgressView.frame = CGRectMake(20, 20, 280, 9);
    [self.view addSubview:self.myProgressView];

}

@end

Usage

Run the application. In the iOS Simulator you should see an app with one button and an empty progress view. You will also see a white activity indicator in the middle of the view. Touch the button two times to start bigTask running in two background threads.

As bigTask runs, you should see the progress view filling up in 10% increments until the task is complete. This process will repeat based on the number of times you touched the button. The progress view should not keep jumping back and forth.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.234.150