Introduction

This tutorial introduces you to the notion of concurrent programming, and performing tasks in the background.  Building on top of the KingsQueens navigator application (Lab 4), it demonstrates how NSOperations can be used to describe the process of downloading images from the within separate threads, without the actual need for managing multi-threaded processes.  It also illustrates how data can be loaded asyncronously, rather than causing the application to stall whilst the data is being loaded (this can be a significant problem if users are using a slow network connection).


To simulate poor network connectivity, a cgi script has been written by Dave Shield to introduce random delays in loading new data.  An update to the JSON file has been generated, which includes calls via this script, which we will use in this lab:


      http://www.csc.liv.ac.uk/people/trp/Teaching_Resources/COMP327/Monarchs9.json


The lab starts where Lab 6 left off.  You can either utilise the code you wrote for that lab, or download the source and use this as a starting point.  A zip file of the complete project is available from here: KingsQueensNav12.zip. Note that this doesn’t include the additional tasks.


Today’s lab is based on the blog post “How To Use NSOperations and NSOperationQueues”, posted by by Soheil Moayedi Azarpour.  Check out the posting for a longer, but more in depth tutorial on NSOperations.  In addition, there is a translation of that post in: Chinese (Simplified).


Todays Tasks:

Walkthrough - Step 1: Loading the Thumbnail Images using NSOperations.


Begin by opening up the source for KingsQueensNav12, and change the name of the JSON file, to use the one above.  Later in the tutorial, we will modify this to introduce download delay, and will look at how we can use asynchronous loading to handle loading such data files.  But for now we will look at the problem of loading al of the thumbnail images.


Compile and Run the app.  What you should notice is that the screen goes white, but then pauses for several seconds whilst the JSON file is initially loaded, followed by the retrieval of all of the thumbnail images.  If you then try to scroll the table, you will notice how jerky and unusable the application is.  This is because every time we scroll the table, causing the cells to be redrawn, we reload the images.  One obvious solution would be to cache the images once loaded, but this would only speed up viewing cells already loaded, rather than new cells.  In addition, each cell has data other than the thumbnail image that might be relevant to the user, and the user might actually want to find a cell to select, without waiting for all of the thumbnail images to load in the mean time.


One could overcome this simply by using threads.  Every application has at least one thread - the main thread, which is responsible for running the application instance itself, including event handling, painting the screen etc, as well as most of the code within an app such as your subclassed view controllers.  However, threads can be tricky to use: threads can access all of your application resources, which means that race conditions may occur on shared objects (hence the need for locking and synchronisation).  It is also possible to suffer from deadlock if resources are not managed properly, and if too many threads execute simultaneously, then this can impair the performance of any single thread - not desirable if the whole idea of taking a multi-threaded approach is to improve the performance of the main thread.


NSOperations are methods that can be queued in an NSOperationQueue, and can be managed in separate threads.  They support dependencies (i.e. don’t start one operation until another operation has completed), priorities (this operation is more / less important than others, cancellation (cancel the operation, whether or not it has started), and pool management (only allow a certain number of operations to run at any point in time, and make the remaining operations wait until one has finished).


Given this, our first step is to get the application to run without loading the images in the main thread.  Instead, we will set up an operation to manage downloading an image, and then create an operation every time an image is required.  Once the image has downloaded, we can then store this and reload the respective cell.


We start by creating an object that manages each image, and that can be queried to check the status of the image.  Create a new object, called KQ2_ThumbnailRecord and inherit from NSObject.  Update the header file to include the following properties, and import the UIKit header file (as we are managing a UIImage here):


#import <Foundation/Foundation.h>

#import <UIKit/UIKit.h>


@interface KQ2_ThumbnailRecord : NSObject


@property (nonatomic, strong) UIImage *image;           // The image itself

@property (nonatomic, strong) NSURL *URL;               // The URL of the image

@property (nonatomic, readonly) BOOL hasImage;          // Return YES if the image has been downloaded

@property (nonatomic, getter = isFailed) BOOL failed;   // Return YES if the image failed to downloaded


@end


Note that in the header file, we have created one property that is readonly, and one with a specific name for the getter accessor method.

We now need to define these two accessor methods in the KQ2_ThumbnailRecord.m source file:


@implementation KQ2_ThumbnailRecord


- (BOOL) hasImage {

    return _image != nil;

}


- (BOOL) isFailed {

    return _failed;

}


@end


So that we can track the status of each operation, we’ll create a new class which inherits from NSObject called KQ2_PendingOperations.  Add the following two properties:


@property (nonatomic, strong) NSMutableDictionary *downloadsInProgress;

@property (nonatomic, strong) NSOperationQueue *downloadQueue;


and then use lazy instantiation within the source to create objects the first time they are accessed:


#pragma mark - Lazy Instantiation


- (NSMutableDictionary *) downloadsInProgress {

    if (!_downloadsInProgress) {

        _downloadsInProgress = [[NSMutableDictionary alloc] init];

    }

    return _downloadsInProgress;

}


- (NSOperationQueue *) downloadQueue {

    if (!_downloadQueue) {

        _downloadQueue = [[NSOperationQueue alloc] init];

        [_downloadQueue setName:@"Download Queue"];

        [_downloadQueue setMaxConcurrentOperationCount:3];  // Allow up to 3 concurrent operations

    }

    return _downloadQueue;

}


Whilst it is possible to actually interrogate an NSOperationQueue to find out the status of a pending operation; creating this object allows us to index operations using the indexPath of the cell in question, thus making the code more efficient.  Note that we have set the maximum concurrent operation count of the queue to three - the greater the number the more operations that can be executed concurrently, but conversely the more likely all of the threads will run slowly.  For the sake of this lab, we have set the number to be 3.


It is now time to create the NSOperation itself.  Whenever creating an NSOperation, there are four steps that should be followed:

  1. 1)Create a class corresponding to a subclass of NSOperation

  2. 2)Override the “main” method

  3. 3)Create an autoreleasepool in “main” (this is consistent with also having to create your own autorelease pool in any threads you define).

  4. 4)Put all of the code within the autoreleasepool block.


Create a new class called KQ2_ThumbnailDownloadOperation, and modify the header file.  Start by importing the KQ2_ThumbnailReord.h so that we can define a property for one of these once the thumbnail image has been successfully loaded.  If the download fails, we can always set it’s failed value to YES.


#import <Foundation/Foundation.h>

#import "KQ2_ThumbnailRecord.h"


@protocol KQ2_ThumbnailDownloadOperationDelegate;


Create a forward declaration to the delegate protocol KQ2_ThumbnailDownloadOperationDelegate as this will define the delegate method that should be called when the thumbnail image has finished downloading.  Also create a property for the delegate that will implement this protocol.


@interface KQ2_ThumbnailDownloadOperation : NSOperation


@property (nonatomic, assign) id <KQ2_ThumbnailDownloadOperationDelegate> delegate;


Add two properties; one to track in which cell the image is to be added (indexPathInTableView) , and a second (thumbnailRecord) for the to store the thumbnail record.  In addition, declare the method initWithThumbnailRecord:atIndexPath:delegate: so that it can be called externally.


@property (nonatomic, readonly, strong) NSIndexPath *indexPathInTableView;

@property (nonatomic, readonly, strong) KQ2_ThumbnailRecord *thumbnailRecord;


- (id) initWithThumbnailRecord:(KQ2_ThumbnailRecord *)record

                   atIndexPath:(NSIndexPath *)indexPath

                      delegate:(id<KQ2_ThumbnailDownloadOperationDelegate>) theDelegate;


@end


Finally, define the protocol that the delegate will commit to:


@protocol KQ2_ThumbnailDownloadOperationDelegate <NSObject>


-(void)thumbnailDownloaderOperationDidFinish:(KQ2_ThumbnailDownloadOperation *)downloader;


@end


Now we can define the KQ2_ThumbnailDownloadOperation.m source file:


Start by creating a private interface for the two properties indexPathInTableView and thumbnailRecord.  In the header file, we set these to readonly - however we will want to write these within our class source.


@interface KQ2_ThumbnailDownloadOperation()

@property (nonatomic, readwrite, strong) NSIndexPath *indexPathInTableView;

@property (nonatomic, readwrite, strong) KQ2_ThumbnailRecord *thumbnailRecord;

@end


Then add the following two methods to the class implementation.  Start by creating the init method declared in the header file; this allows the caller to initialise a newly created instance of this class (i.e. an object) with a set of parameters:


#pragma mark - LifeCycle


- (id) initWithThumbnailRecord:(KQ2_ThumbnailRecord *)record

                   atIndexPath:(NSIndexPath *)indexPath

                      delegate:(id<KQ2_ThumbnailDownloadOperationDelegate>) theDelegate {

    if (self = [super init]) {

        [self setDelegate:theDelegate];

        [self setIndexPathInTableView:indexPath];

        [self setThumbnailRecord:record];

    }

    return self;

}


Then create the overrided main method.  There are a couple of things to note here.  It is possible to cancel an operation by sending the method cancel to it; and in fact one can cancel all of the operations in a queue simply by calling cancelAllOperations.  This is useful to prevent any unwanted processing (e.g. the user was transitioning to another screen).  However, it is the responsibility of the developer to catch the cancelled message.  Hence, within the main method, prior to doing any significant processing, we check to see if the operation has been cancelled, and if so, we clean up and stop.


#pragma mark - Download thumbnail


- (void) main {

    @autoreleasepool {

        // Always check if the operation has been cancelled as frequently as possible

        if ([self isCancelled])

            return;

       

        NSData *imageData = [[NSData alloc] initWithContentsOfURL:[[self thumbnailRecord] URL]];

       

        if ([self isCancelled]) {

            imageData = nil;

            return;

        }

       

        if (imageData) {

            UIImage *downloadedImage = [UIImage imageWithData:imageData];

            [[self thumbnailRecord] setImage:downloadedImage];

        } else {

            [[self thumbnailRecord] setFailed:YES];

        }

        imageData = nil;

       

        if ([self isCancelled])

            return;


        [(NSObject *)[self delegate] performSelectorOnMainThread:@selector(thumbnailDownloaderOperationDidFinish:)

                                                      withObject:self

                                                   waitUntilDone:NO];

    }

   

}


Look carefully at what happens in this method.  We start by loading the data from the URL.  Then we convert this into an image if successful.  And then once done, we call the selector thumbnailDownloaderOperationDidFinish on the main thread to notify the main thread that the operation has completed.


We are now ready to wire this into our main code.  Start by including the new header files in KQ2_MainViewController.h, and commit the implementation to the new protocol:


#import <UIKit/UIKit.h>

#import "KQ2_ThumbnailRecord.h"

#import "KQ2_PendingOperations.h"

#import "KQ2_ThumbnailDownloadOperation.h"


// New protocol on the interface

@interface KQ2_MainViewController : UITableViewController <KQ2_ThumbnailDownloadOperationDelegate>


@end


In the source file in KQ2_MainViewController.m, declare a new property in the class category:


@property (strong, nonatomic) KQ2_PendingOperations *pendingOperations;


Then within the implementation section, add a lazy instantiation of pendingOperations.


- (KQ2_PendingOperations *)pendingOperations {

    if (!_pendingOperations) {

        _pendingOperations = [[KQ2_PendingOperations alloc] init];

    }

    return _pendingOperations;

}


Unlike the original KingsQueens Navigator app, in this app we want to store (cache) instances of the new class KQ2_ThumbnailRecord for each of the images loaded (or planned to be loaded).  We could modify the implementation of myKingsQueensDataModel so that  when it is constructed from the JSON data, we actually replace the NSDictionary for each monarch with an NSMutableDictionary.  However, in this case, as each of the entries in myKingsQueensDataModel will be obtained by using an NSIndexPath, we can create a separate NSMutableDictionary that is indexed by the same key (indexPath) which stores our KQ2_ThumbnailRecord objects.


Add the property @property (strong, nonatomic) NSMutableDictionary *thumbnailStoreDict; and implement the getter method using lazy instantiation:


- (NSMutableDictionary *) thumbnailStoreDict {

    if (!_thumbnailStoreDict) {

        _thumbnailStoreDict = [[NSMutableDictionary alloc] init];

    }

    return _thumbnailStoreDict;

}


We will modify the method tableView:cellForRowAtIndexPath: to instigate each new operation when a cell is required (assuming the thumbnail image hasn’t already been loaded).  To provide some form of user feedback, we will add an activity indicator to our custom cell.  Position the activity indicator on the cell, and select the style you like (I’ve gone for the small gray style).  You might also want to tick the box “Hides When Stopped” so that the indicator only appears when it is active.  Add the IBOutlet property UIActivityIndicatorView *activityIndicatorView to the custom class KQ2_CustomCell.h.


@property (strong, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicatorView;


We can now modify the method tableView:cellForRowAtIndexPath: to manage the image.  Comment out the code which we originally used to load the thumbnail images, and replace it with a call to retrieve a KQ2_ThumbnailRecord record from the thumbnailStoreDict.


    // ============================================

    // Display thumb images

    // NSURL *url = [NSURL URLWithString:[[monarchs objectAtIndex:[indexPath row]] objectForKey:@"thumb"]];

    // UIImage *thumbImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];

    // [[cell thumbImageView] setImage:thumbImage];

    KQ2_ThumbnailRecord *record = [[self thumbnailStoreDict] objectForKey:indexPath];


However, the very first time the cells are created for an indexPath, no such record will exist.  So we need to check for this case, and if the object record is nil, then create one:


    if (record == nil) {

        // The object doesn't exist - in which case we need to create it and add it to the store

        record = [[KQ2_ThumbnailRecord alloc] init];

       

        NSURL *url = [NSURL URLWithString:[[monarchs objectAtIndex:[indexPath row]] objectForKey:@"thumb"]];

        [record setURL:url];

        // Add the record to the thumbnail store

        [[self thumbnailStoreDict] setObject:record forKey:indexPath];

    }

   

At this point, we have a legal object of type KQ2_ThumbnailRecord.  We can now check its status, and respond accordingly.  If the image has been downloaded, then simply display it.  In case the activity indicator had been animated, stop the animation.  If for whatever reason the download failed, then we need to alert the user to the fact.  In this case, we will simply display a “failed” image.  Again, stop any activity indicator animation.  Finally, if the image has neither been downloaded or failed, then we need to do something about creating an operation.  For now we will call the method startOperationsForThumbnailRecord:atIndexPath:, which is then implemented below - this will be responsible for checking if a corresponding operation exists, and if not, it creates one.


    // Check to see if we have an image; if so, then display it!

    if ([record hasImage]) {

        [[cell activityIndicatorView] stopAnimating];

        [[cell thumbImageView] setImage:[record image]];

    } else if ([record isFailed]) {

        // Uh oh - couldn't download image

        [[cell activityIndicatorView] stopAnimating];

        [[cell thumbImageView] setImage:[UIImage imageNamed:@"Failed.png"]];

    } else {

        // Ok - so this is where we create the operation

        [[cell activityIndicatorView] startAnimating];

        [[cell thumbImageView] setImage:[UIImage imageNamed:@"Placeholder.png"]];

        [self startOperationsForThumbnailRecord:record atIndexPath:indexPath];

    }


    // ============================================


Note that we assume the existence of two images “Failed.png” and “Placeholder.png”.  The following zip file Resources.zip contains a directory of four images (normal and “retina” versions of each image) taken from the “How To Use NSOperations and NSOperationQueues” blog post.  Download these and add them to your project.


The method startOperationsForThumbnailRecord:atIndexPath: is responsible for checking to see if an operation has been constructed for the cell at indexPath.  This is checked  by looking at the dictionary downloadsInProgress, and seeing if the index path is one of the keys for any of the objects - i.e. it checks to see if there is an object with that index path.  If not, we then create an entry in the dictionary for an instance of the new operation, and then we add the operation to the downloadQueue.  This last step queues the operation, which will be executed on a separate thread when possible.


- (void) startOperationsForThumbnailRecord:(KQ2_ThumbnailRecord *)record atIndexPath:(NSIndexPath *)indexPath{

    if (![record hasImage]) {

        if (![[[[self pendingOperations] downloadsInProgress] allKeys] containsObject:indexPath]) {

            // We don't have a pending operation for this index path, so create one

           

            KQ2_ThumbnailDownloadOperation *downloadOp = [[KQ2_ThumbnailDownloadOperation alloc] initWithThumbnailRecord:record

                                                                                                             atIndexPath:indexPath

                                                                                                                delegate:self];

            [[[self pendingOperations] downloadsInProgress] setObject:downloadOp forKey:indexPath];

            [[[self pendingOperations] downloadQueue] addOperation:downloadOp];

        }

    }

}


We are now almost done.  The very last thing to do is to implement the method defined in the KQ2_ThumbnailDownloadOperationDelegate protocol - this method will be called when the image has finally been loaded (i.e. just as the operation finishes).  This last method reloads the cell corresponding to the indexpath of the completed operation, and then deletes this operation from the pending operations queue.


// Implement the protocol

-(void)thumbnailDownloaderOperationDidFinish:(KQ2_ThumbnailDownloadOperation *)downloader {

    NSIndexPath *indexPath = [downloader indexPathInTableView];

    // KQ2_ThumbnailRecord *record = [downloader thumbnailRecord];

    [[self tableView] reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];

    [[[self pendingOperations] downloadsInProgress] removeObjectForKey:indexPath];

}


Done!  Compile the code and test it.


Walkthrough - Step 2: Loading files on a separate thread with Grand Central Dispatch.


In iOS3.2, a new approach for supporting multithreading was introduced called Grand Central Dispatch.  Instead of worrying about the creation and management of threads, Grand Central Dispatch would look after this for you, such as creating new threads when necessary, or reusing an old thread if one was available.  In fact, NSOperations are built on top of Grand Central Dispatch, and offer a number of additional features such as operation dependency (for example, if there are two separate operations that run over the same data, this ensures that the first operation completes before the second starts).  In this step, we will demonstrate how Grand Central Dispatch can be easily used to run a fragment of code in another thread, and how we can incorporate this into our code.


// static NSString *myJSONDataSrc = @"http://www.csc.liv.ac.uk/people/trp/Teaching_Resources/COMP327/Monarchs9.json";

static NSString *myJSONDataSrc = @"http://www.csc.liv.ac.uk/people/trp/Teaching_Resources/COMP327/delay2.php?delay=5&url=http://www.csc.liv.ac.uk/people/trp/Teaching_Resources/COMP327/Monarchs9.json";



To simulate a delay in the network, we will continue to use the delaying script used for the photos.  Change the URL of the JSON file to the URL above.  Note we have commented out the old link.


Note, if you are developing on a device, which is connected to Xcode, you can also use the developer settings to simulate poor network conditions.  Open the settings panel, once the device has been connected to the machine on which you are developing, and look for the “Developer” option (note this only appears when the machine is connected, and being used for development.  You will then see a number of other settings that can be switched on.  To simulate different network settings, select the Network Link Conditioner Status cell, and choose one of the options (using the “Edge” preset is a good choice for slower networks), and then switch Enable to on.


If you use this, don’t forget to then switch it off when you are finished


If you run this, you will now notice that the application appears to freeze for several seconds, before the cells appear, and the images start to load.  Currently, we rely on two methods to load the data; the first is the getter method myKingsQueensDataModel which loads the data itself, and the second, monarchHouseKeys, which is actually used to construct the table.  If we move the loading of the data into a separate thread, we need to be sure that the application will not crash (or at least behave unpredictably), whilst we are waiting for the data to load.  Also, ideally we need a visual cue to notify the user that something is happening.


We’ll start by loading the data in a separate thread.  Modify the method myKingsQueensDataModel in KQ2_MainViewController.m so that the loading and parsing of the JSON data occurs in a code block, which is then passed to the method dispatch_async.  Note that we have also introduced the property downloadJSONInProgress, which we use here to indicate that the data is being loaded.  The first time this method is called, _myKingsQueensDataModel will be nil.  This will then trigger the loading of the data.  However, once that has been triggered, _myKingsQueensDataModel will still be nil, as the data will be loaded in the background.  Therefore, in this case, we use the new property to track whether or not we have initiated the download.


- (NSDictionary *) myKingsQueensDataModel {

    if ((_myKingsQueensDataModel == nil) && ([self downloadJSONInProgress] == NO)) {

        // Calling the getter method for the first time, so we need to load the data.

        NSLog(@"Calling accessor method myKingsQueensDataModel for the first time...");

       

        // ================================

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

            // Generate the URL request for the JSON data

            NSURL *url = [NSURL URLWithString:myJSONDataSrc];

            NSData *data = [NSData dataWithContentsOfURL:url];

            [self performSelectorOnMainThread:@selector(fetchedKingsQueensDataModel:)

                                   withObject:data waitUntilDone:YES];

        });

        [self setDownloadJSONInProgress:YES];

    }

    return _myKingsQueensDataModel;

}


You will also need to create the property - add this to the other properties defined within the KQ2_MainViewController class category.  Note that because the type of the property is a primitive data type and not a Foundation Object, we just use assign (the notion or retain count here is meaningless).


@property (assign, nonatomic) BOOL downloadJSONInProgress;


If you look in the new myKingsQueensDataModel, you will notice that in the code fragment, we create the url, load the data, but then call the method performSelectorOnMainThread::, passing in the selector @selector(fetchedKingsQueensDataModel:).  This will call the method fetchedKingsQueensDataModel: and pass in as an argument data when the download has finished.  Add this new method:


- (void) fetchedKingsQueensDataModel:(NSData *)data {

    NSError *error;

    // Note that we are not calling the setter method here... as this would be recursive!!!

    _myKingsQueensDataModel = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];

   

    // Reset the download property, as we have now finished the download

    [self setDownloadJSONInProgress:NO];


    // Reset the monarchHouseKeys object to nil, as this will force it to be reinstantiated when

    // the table is re-loaded.  Then reload the table

    [self setMonarchHouseKeys:nil];

    [[self tableView] reloadData];

}


This method is responsible for parsing the JSON and ensuring that the table is reloaded.  Although we could have parsed the JSON data in the dispatched code fragment, we can do it here as it is a relatively inexpensive operation.  More importantly, we would not want the dispatched code to modify the property _myKingsQueensDataModel (without at least introducing asynchronous locks) as this could also be used by the main thread.  By passing the data to this method in the main thread, and then getting that to update the property, we avoid such problems.  Finally, we reset the property downloadJSONInProgress to NO, set the monarchHouseKeys property to nil (which will cause it to be lazy instantiated when next used), and force a reload on the tableView.


Compile and text.  An empty table should appear for a few seconds, before the table filling up with new cells.


Additional Task 1

The one thing that is still missing is some form of visual feedback to inform the user that data is being downloaded. Create a UIActivityIndicator programmatically within the viewDidLoad method of KQ2_MainViewController.m, and start it spinning when new data is about to load.  Don’t forget to stop the spinning when the data has been loaded.


Additional Task 2

It is also possible to set the network activity icon (in the status bar) to also appear, to indicate that there is network activity.  Add the following line to the method myKingsQueensDataModel once the data loading fragment has been dispatched.  Add a similar line (but with the argument set to NO) at the end of fetchedKingsQueensDataModel: and test out the code.  Whilst this can be very effective for single downloads, managing the indicator can be challenging when you have multiple operations (such as in step 1).  Think how you might get around this problem.


        [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];



Full Code

A zip file of the complete project is available from here: (coming soon).  Note that this doesn’t include the additional tasks.