URL Image
            or
Delegates at Work

Modified

Downloads

UIImageDelegate example

Overview

Images can be downloaded by URL and displayed in the UI.

  1. Can download on the UI thread, easy but bad idea.
     
  2. Can download using delegates with most work executed off the UI thread, much better idea.

On the UI thread

Easy but can cause the UI to be unresponsive.

myImage should be connected to a UIImageView object.

URLImageUIThread.h
#import <UIKit/UIKit.h>

@interface HW3ViewController : UIViewController {
    IBOutlet UIImageView * myImage;    
}
@end
URLImageUIThread.m
#import "URLImageUIThread.h"

@implementation URLImageUIThread
- (void)viewDidLoad{
    [super viewDidLoad];
    NSString * myURL = @"http://java.sogeti.nl/JavaBlog/wp-content/uploads/2009/04/android_icon_256.png";

    UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:myURL]]];

    myImage.image = image;
}
@end

Blocking calls on Main Loop

Most method calls block execution until the call is complete.

The operation to download the image executes a blocking method call:

UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:myURL]]];

 is followed by the statement that uses the image.

myImage.image = image

which cannot execute until the blocking call executes. So there is sequential execution within the same thread, in this case, the main loop.

 

 

Problems in the Event Loop

(http://developer.apple.com/library/ios/#documentation/iphone/conceptual/iphoneosprogrammingguide/CoreApplication/CoreApplication.html

The previous example works but hogs the event loop for the duration of the download.

The diagram at right illustrates processing events in event loop. The main idea is that each event results in calling iOS and a call-back from iOS.

  1. UI event occurs (user touches a button).
  2. Executes an iOS function.
  3. Places event in queue.
  4. Event passed to main run loop.
  5. Application object (button) code executes, perhaps to handle Touch up Inside.
  6. Calls core object (where button is implemented).
  7. Core object calls iOS function.
  8. iOS function updates UI.

The key points are:

  1. A long execution can hog the main loop.
     
  2. After the call-back, other events have a chance to execute.

Delegates

Delegate methods can often be used to break up one long-running execution into several, shorter ones.

NSURLConnection executes most of the background work on a separate thread.

NSURLConnection executes call-backs to delegate methods when work for the delegate:

- (void)connection:(NSURLConnection *)theConnection didReceiveData:(NSData *)incrementalData

when data is received.

- (void)connectionDidFinishLoading:(NSURLConnection*)theConnection

when all data received and the connection closes.

These methods execute on the delegate's own thread.

Using delegates

The following app is a delegate of NSURLConnection.

#import <UIKit/UIKit.h>

@interface UIImageLoadViewController : UIViewController{
    IBOutlet UIImageView * myImage;    
    NSURLConnection* connection;
    NSMutableData* data;
}
@end
#import "UIImageLoadViewController.h"

@implementation UIImageLoadViewController

- (void)viewDidLoad  {
    [super viewDidLoad];
    NSString * myURL = 
            @"http://java.sogeti.nl/JavaBlog/wp-content/uploads/2009/04/android_icon_256.png";
    
    

    NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString: myURL ]
                                                cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                 timeoutInterval:60.0];

    connection = [[NSURLConnection alloc] initWithRequest:request delegate: self];

}

- (void)connection:(NSURLConnection *)theConnection 
            didReceiveData:(NSData *)incrementalData {
    if (data==nil) data = [[NSMutableData alloc] initWithCapacity:2048];
    [data appendData:incrementalData];
}

- (void)connectionDidFinishLoading:(NSURLConnection*)theConnection {    
    [connection release];
    connection=nil;
    myImage.image = [UIImage imageWithData:data];
    [data release];
    data=nil;
}

@end

 

Writing delegate class - AsyncImage implementation

To understand how delegates work, the following re-implements the image download by creating our own delegate class.

AsyncImage class downloads an image through the NSURLConnection call-backs above.

AsyncImage is a delegate of NSURLConnection.

 connection = [[NSURLConnection alloc] initWithRequest:request delegate: self ];

AsyncImage is initialized with a delegate and runs on the delegate's thread, in this case the UI thread.

asyncImage = [[AsyncImage alloc] initWithURL:[NSURL URLWithString: myURL ] delegate: self ];

When the image data is downloaded completely, AsyncImage makes a call-back to pass a UIImage of the data to its delegate.

[delegate imageDownLoaded: [UIImage imageWithData:data]];

The delegate can then update the UI with the image.

-(void) imageDownLoaded: (UIImage *) image {
    myImage.image = image;
    [asyncImage release];
}

Is this a good idea, given that NSURLConnection generates call-back events to the NSURLConnection methods implemented in delegate AsyncImage?

The answer has to do with whose thread is executing the delegate methods and how much time is spent in executing those methods.

Delegate methods are expected to execute in the delegate's thread, so we would expect NSURLConnection methods to execute in whatever thread was executing AsyncImage.

When the time spent in each execution of a delegate method is small and the number of executions is few, the main or UI thread could handle the executions without unreasonable delays.

When the time spent in each execution of a delegate method is large or the number of executions is many, the main or UI thread may not handle the executions without unreasonable delays. A separate thread should be execute the delegate methods.

NSURLConnection runs on its own thread, handling the relatively slow Internet download.

When NSURLConnection executes a call-back to AsyncImage, execution takes place on the UI thread that initialized AsyncImage.

Each call-back is completed fairly quickly in  AsyncImage and the UI thread continues.

Though slightly more effort, delegate makes the app more responsive uses delegates.

  1. myImage should be connected to a UIImageView object.
     
  2. [[AsyncImage alloc] initWithURL:[NSURL URLWithString: myURL ] delegate: self]; starts loading an image from myURL and initializes the calling class object as the delegate.
     
  3. -(void) imageDownLoaded: (UIImage *) image; must be implemented by the delegate class loading the image.
URLImageDelegate.h
#import <UIKit/UIKit.h>

@interface URLImageDelegate : UIViewController {
    IBOutlet UIImageView * myImage;    
}
-(void) imageDownLoaded: (UIImage *) image;
@end
URLImageDelegate.m
#import "URLImageDelegate.h"

@interface AsyncImage : NSObject {
    NSURLConnection* connection;
    NSMutableData* data;
    id delegate;
}
-(void)loadImageFromURL:(NSURL*)url;
-(AsyncImage *) initWithURL: (NSURL *) url delegate: (id) d;
@end

@implementation AsyncImage

-(AsyncImage *) initWithURL: (NSURL *) url delegate: (id) d {
    delegate = d;
    [self loadImageFromURL: url];
    return self;
}
            
- (void) loadImageFromURL:(NSURL*)url {
    if (connection!=nil) [connection release];
    if (data!=nil) [data release];

    NSURLRequest* request = [NSURLRequest requestWithURL:url
                            cachePolicy:NSURLRequestUseProtocolCachePolicy
                            timeoutInterval:60.0];
    connection = [[NSURLConnection alloc] initWithRequest:request delegate: self ];
}

- (void)connection:(NSURLConnection *)theConnection didReceiveData:(NSData *)incrementalData {
    if (data==nil) 
              data = [[NSMutableData alloc] initWithCapacity:2048];
    [data appendData:incrementalData];
}

- (void)connectionDidFinishLoading:(NSURLConnection*)theConnection {    
    [connection release];
    connection=nil;
    [delegate imageDownLoaded: [UIImage imageWithData:data]];
    [data release];
    data=nil;
}

- (void)dealloc {
    [connection cancel];
    [connection release];
    [data release];
    [super dealloc];
}
@end
@implementation URLImageDelegate

AsyncImage * asyncImage;

- (void)viewDidLoad{
    [super viewDidLoad];
    NSString * myURL =
         @"http://java.sogeti.nl/JavaBlog/wp-content/uploads/2009/04/android_icon_256.png";
 
   asyncImage = [[AsyncImage alloc] initWithURL:[NSURL URLWithString: myURL ] delegate: self];

}

-(void) imageDownLoaded: (UIImage *) image {
    myImage.image = image;                                           // Display image
    [asyncImage release];
}

@end

Is Main Loop Executing?

The operation to download the image:

asyncImage = [[AsyncImage alloc] initWithURL:[NSURL URLWithString: myURL ] delegate: self];

 is not followed by the statement that uses the image. 

myImage.image = image;                                           // Display image

Execution must flow through delegate methods so there is not sequential execution within the same thread, in this case, the main loop.

While downloading the image, the main loop is executed through two call-backs:

- (void)connection:(NSURLConnection *)theConnection didReceiveData:(NSData *)incrementalData;

- (void)connectionDidFinishLoading:(NSURLConnection*)theConnection;