Introduction

In this blog article I follow-up a previous article on Managing Complex Mobile Transactions with a simple working example written in Objective-C for the iPhone.

The previous article describes a pattern for managing the complex error conditions that may occur. This article provides an example of calling an authenticated service from YouTube. In this type of communication there are a number of failure modes that need to be considered.

  1. The user may not provide valid credentials
  2. The device may not be on a functional network
  3. YouTube may not respond in a timely manner or may fail for some reason.

The application needs to handle each of these conditions in an elegant and reliable manner. This article will survey the major code components and discuss some of the implementation details.

The app in the included project is a simple demonstration app. It is not intended for anything other than demonstrating this pattern. It does not demonstrate my UI design skills or a full implementation of YouTube's APIs.

Prerequisites

First, the things you'll need to have to successfully see this app operate:

  1. A YouTube account
  2. For best results you may want to have at least 1 video uploaded to YouTube. It doesn't need to be public, just uploaded by the account.
  3. A working understanding of XCode, Objective-C, and the iOS SDK.
  4. Download the project zip file attached to this blog article.

This project was developed using XCode 4.1 and iOS 4.3. The application was developed using the YouTube API as it stood in October of 2011. It is under Google's control and may change.

Major Objects

Once you've downloaded the project and loaded it up in XCode you'll see the following classes.

Commands

In the commands group you'll find:

BaseCommand: The BaseCommand object is the superclass for all the command objects. It provides many methods that are needed by every command class. These methods include:

  • Methods to send completion, error, and login needed notifications.
  • A method to help issuing objects listen for completion notifications.
  • Methods used to support the actual NSURLRequests

BaseCommand extends NSOperation so all of the logic of the command is in the main method of each subclass of this object.

GetFeed: This command calls YouTube and loads the list of videos uploaded by the currently logged in user. YouTube determines the identify of the logged in user by a token passed in an HTTP header on the request. Without that header YouTube will return a status code of 0.

<span class="sy0">-</span> <span class="br0">(</span><span class="kw4">void</span><span class="br0">)</span>main <span class="br0">{</span> NSLog<span class="br0">(</span><span class="co3">@</span><span class="st0">"Starting getFeed operation"</span><span class="br0">)</span>; <span class="co2">// Check to see if the user is logged in if ([self isUserLoggedIn]) { // on do this if the user is logged in // Build the request NSString *urlStr=@"https://gdata.youtube.com/feeds/api/users/default/uploads"; NSLog(@"urlStr=%@",urlStr); NSMutableURLRequest *request = [ self createRequestObject:[NSURL URLWithString:urlStr]]; // Sign the request with the user's auth token [self signRequest:request]; // Send the request NSHTTPURLResponse *response=nil; NSError *error=nil; NSData *myData = [self sendSynchronousRequest:request response_p: &response error:&error]; // Check to see if the request was successful if ([super wasCallSuccessful:response error:error]) { [self buildDictionaryAndSendCompletionNotif: myData]; } } } </span>

In this method, many of the methods called on self are implemented in the BaseCommand superclass. The GetFeed command is prototypical of the command dispatch pattern. The main method checks to make sure the user is logged in (line 7) because there's no reason to call the server if we know this call will fail. If the user is logged in then the code builds the request, lines 11-15, adds the auth header to it, line 19, then sends a synchronous request (line 28). Line 33 calls a superclass method to determine if the call succeeded. This method uses both the NSError object and the status code from the NSHTTPURLResponse object to determine success. If the call failed then either an error notification or login needed notification is broadcast.

LoginCommand: This method sends the request to YouTube to authenticate the user. This command is somewhat more involved because it doesn't use several of the helper methods found in the BaseCommand object. It does not use these methods because it should not generate a ‘needs authentication' failure message if the login fails. It will only report a status of good completion or failed completion. The login listener will handle the errors that come from failed login attempts. Information on the protocol that YouTube requires see: YouTube Data API

The key code in the listeners is found in the viewDidDisappear method. This method is called when the view has completely disappeared. If the commands are requeued before the view has completely disappeared there is a chance that another error may trigger the re-presentation of the view, thereby causing a fatal error in the application. iOS 5 has a better capacity to handle this case because one can specify a block of code to execute when the view disappears. The code will not have to determine the cause of the disappearance before handling the triggering commands.

<span class="sy0">-</span> <span class="br0">(</span><span class="kw4">void</span><span class="br0">)</span> viewDidDisappear<span class="sy0">:</span><span class="br0">(</span><span class="kw4">BOOL</span><span class="br0">)</span>animated <span class="br0">{</span> <span class="kw1">if</span> <span class="br0">(</span>retryFlag<span class="br0">)</span> <span class="br0">{</span> <span class="co2">// re-enqueue all of the failed commands [self performSelectorAndClear:@selector(enqueueOperation)]; } else { // just send a failure notification for all failed commands [self performSelectorAndClear:@selector(sendCompletionFailureNotification)]; } self.displayed = NO; }</span>

In the listeners group you'll find the view controllers that are presented when an error occurs or when the user needs to login. Both the NetworkErrorViewController and the LoginViewController extend theInterstitialViewController which provides several common helper methods. Both view controllers are presented as modal view controllers.

The NetworkErrorViewController provides the user the choice of retrying or aborting the failed operations. If the user selects retry then the failed commands are placed back on the operation queue.

The LoginViewController solicits a username and password from the user. It will stay at the top of the view stack until the user successfully logs in.

The application delegate registers itself as the listener for both network error and login needed notifications. It manages the presentation of proper view controller when an error occurs.

The code below is the notification handler for the login needed notification. Because it is dealing with the user interface it's contents must be executed on the main thread. The code is using GrandCentral dispatch to execute those blocks on the main thread.

<span class="coMULTI">/** Handles login needed notifications generated by commands **/</span> <span class="sy0">-</span> <span class="br0">(</span><span class="kw4">void</span><span class="br0">)</span> loginNeeded<span class="sy0">:</span><span class="br0">(</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSNotification_Class/"><span class="kw5">NSNotification</span></a> <span class="sy0">*</span><span class="br0">)</span>notif <span class="br0">{</span> dispatch_async<span class="br0">(</span>dispatch_get_main_queue<span class="br0">(</span><span class="br0">)</span>, <span class="sy0">^</span><span class="br0">{</span> <span class="co2">// make sure it all occurs on the main thread @synchronized(loginViewController) { // make sure only one thread adds a command at a time [loginViewController addTriggeringCommand:; if (!loginViewController.displayed) { // if the view is not displayed then display it. ) { NSArray *entryArray = [NSArray arrayWithObject:entries]; [[feed objectForKey:@"feed"] setObject:entryArray forKey:@"entry"]; } dispatch_async(dispatch_get_main_queue(), ^{ [self.tableView reloadData]; }); } else { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"No Videos" message:@"The login to YouTube failed" delegate:self cancelButtonTitle:@"Retry" otherButtonTitles:nil]; [alert show]; [alert release]; } }</span>

The class YouTubeVideoCell is a UITableViewCell subclass that asynchronously loads the thumbnail of a video. It uses the LoadImageCommand object to accomplish this.

<span class="coMULTI">/** Start the process of loading the image via the command queue **/</span> <span class="sy0">-</span> <span class="br0">(</span><span class="kw4">void</span><span class="br0">)</span> startImageLoad <span class="br0">{</span> LoadImageCommand <span class="sy0">*</span>cmd <span class="sy0">=</span> <span class="br0">[</span><span class="br0">[</span>LoadImageCommand alloc<span class="br0">]</span> init<span class="br0">]</span>; cmd.imageUrl <span class="sy0">=</span> imageUrl; cmd.completionNotificationName <span class="sy0">=</span> imageUrl; <span class="co2">// set the name to something unique [cmd listenForMyCompletion:self selector:@selector(didReceiveImage:)]; [cmd enqueueOperation]; [cmd release]; }</span>

Notice that the issuing class changes the completion notification name (line 13) . It does this so that it, and only it, will receive a notification for this particular image. Otherwise it would need to examine the returned notification to see if it were the command that it originally issued.

Conclusion

Attached to this article is the XCode project containing the entire code of the demonstration project. Happy coding.