Performing UI-Related Tasks

UI-related tasks have to be performed on the main thread, so the main queue is the only candidate for UI task execution in GCD. We can use the dispatch_get_main_queue function to get the handle to the main dispatch queue.

There are two ways of dispatching tasks to the main queue. Both are asynchronous, letting your program continue even when the task is not yet executed:

dispatch_async function

Executes a block object on a dispatch queue.

dispatch_async_f function

Executes a C function on a dispatch queue.

Note

The dispatch_sync method cannot be called on the main queue because it will block the thread indefinitely and cause your application to deadlock. All tasks submitted to the main queue through GCD must be submitted asynchronously.

Let’s have a look at using the dispatch_async function. It accepts two parameters:

Dispatch queue handle

The dispatch queue on which the task has to be executed.

Block object

The block object to be sent to the dispatch queue for asynchronous execution.

Here is an example. This code will display an alert, in iOS, to the user, using the main queue:

dispatch_queue_t mainQueue = dispatch_get_main_queue();

dispatch_async(mainQueue, ^(void) {

  [[[[UIAlertView alloc]
     initWithTitle:NSLocalizedString(@"GCD", nil)
     message:NSLocalizedString(@"GCD is amazing!", nil)
     delegate:nil
     cancelButtonTitle:NSLocalizedString(@"OK", nil)
     otherButtonTitles:nil, nil] autorelease] show];

});

Note

As you’ve noticed, the dispatch_async GCD function has no parameters or return value. The block object that is submitted to this function must gather its own data in order to complete its task. In the code snippet that we just saw, the alert view has all the values that it needs to finish its task. However, this might not always be the case. In such instances, you must make sure the block object submitted to GCD has access in its scope to all the values that it requires.

Running this app in iOS Simulator, the user will get results similar to those shown in Figure 2-1.

This might not be that impressive. In fact, it is not impressive at all if you think about it. So what makes the main queue truly interesting? The answer is simple: when you are getting the maximum performance from GCD to do some heavy calculation on concurrent or serial threads, you might want to display the results to your user or move a component on the screen. For that, you must use the main queue, because it is UI-related work. The functions shown in this section are the only way to get out of a serial or a concurrent queue while still utilizing GCD to update your UI, so you can imagine how important it is.

Instead of submitting a block object for execution on the main queue, you can submit a C function object. Submit all UI-related C functions for execution in GCD to the dispatch_async_f function. We can get the same results as we got in Figure 2-1, using C functions instead of block objects, with a few adjustments to our code.

As mentioned before, with the dispatch_async_f function, we can submit a pointer to an application-defined context, which can then be used by the C function that gets called. So here is the plan: let’s create a structure that holds values such as an alert view’s title, message, and cancel-button’s title. When our app starts, we will put all the values in this structure and pass it to our C function to display. Here is how we are defining our structure:

typedef struct{
  char *title;
  char *message;
  char *cancelButtonTitle;
} AlertViewData;

Now let’s go and implement a C function that we will later call with GCD. This C function should expect a parameter of type void *, which we will then typecast to AlertViewData *. In other words, we expect the caller of this function to pass us a reference to the data for our alert view, encapsulated inside the AlertViewData structure:

void displayAlertView(void *paramContext){

  AlertViewData *alertData = (AlertViewData *)paramContext;

  NSString *title =
  [NSString stringWithUTF8String:alertData->title];

  NSString *message =
  [NSString stringWithUTF8String:alertData->message];

  NSString *cancelButtonTitle =
  [NSString stringWithUTF8String:alertData->cancelButtonTitle];

  [[[[UIAlertView alloc] initWithTitle:title
                               message:message
                              delegate:nil
                     cancelButtonTitle:cancelButtonTitle
                     otherButtonTitles:nil, nil] autorelease] show];

  free(alertData);

}

Note

The reason we are freeing the context passed to us in here instead of in the caller is that the caller is going to execute this C function asynchronously and cannot know when our C function will finish executing. Therefore, the caller has to malloc enough space for the AlertViewData context and our displayAlertView C function has to free that space.

And now let’s call the displayAlertView function on the main queue and pass the context (the structure that holds the alert view’s data) to it:

- (BOOL)              application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{

  dispatch_queue_t mainQueue = dispatch_get_main_queue();

  AlertViewData *context = (AlertViewData *)
    malloc(sizeof(AlertViewData));

  if (context != NULL){
    context->title = "GCD";
    context->message = "GCD is amazing.";
    context->cancelButtonTitle = "OK";

    dispatch_async_f(mainQueue,
                     (void *)context,
                     displayAlertView);
  }

  // Override point for customization after application launch.
  [self.window makeKeyAndVisible];
  return YES;
}

If you invoke the currentThread class method of the NSThread class, you will find out that the block objects or the C functions you dispatch to the main queue are indeed running on the main thread:

- (BOOL)              application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{

  dispatch_queue_t mainQueue = dispatch_get_main_queue();

  dispatch_async(mainQueue, ^(void) {
    NSLog(@"Current thread = %@", [NSThread currentThread]);
    NSLog(@"Main thread = %@", [NSThread mainThread]);
  });

  // Override point for customization after application launch.
  [self.window makeKeyAndVisible];
  return YES;
}

The output of this code would be similar to that shown here:

Current thread = <NSThread: 0x4b0e4e0>{name = (null), num = 1}
Main thread = <NSThread: 0x4b0e4e0>{name = (null), num = 1}

Now that you know how to perform UI-related tasks using GCD, it is time we moved to other subjects, such as performing tasks in parallel using concurrent queues (see Performing Non-UI-Related Tasks Synchronously and Performing Non-UI-Related Tasks Asynchronously) and mixing our code with UI-related code if need be.