views:

47

answers:

1

Hi,

I would like to add to UIImageView the capacity to set an image with an url. As result I would like to do something like.

[anImageView setImageWithContentAtUrl:[NSURL URLWithString:@"http://server.com/resource.png"]];

So I created a category (code below).

NSString *kUserInfoImageViewKey = @"imageView";
NSString *kUserInfoActivityIndicatorKey = @"activityIndicator";

@implementation UIImageView (asynchronous)

#pragma mark -
- (void)setImageWithContentAtUrl:(NSURL *)imageUrl andActivityIndicator:(UIActivityIndicatorView *)activityIndicatorOrNil {
   [activityIndicatorOrNil startAnimating];

 NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
 [dict setValue:self forKey:kUserInfoImageViewKey];
 [dict setValue:activityIndicatorOrNil forKey:kUserInfoActivityIndicatorKey];

 ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:imageUrl];
 request.delegate = self;
 request.userInfo = dict;
   [dict release];
 [request startAsynchronous];
}

#pragma mark -
#pragma mark private

- (void)requestFinished:(ASIHTTPRequest *)aRequest {
 // get concerned view from user info
 NSDictionary *dictionary = aRequest.userInfo;
   UIImageView *imageView = (UIImageView *)[dictionary valueForKey:kUserInfoImageViewKey];
 UIActivityIndicatorView *activityIndicator = (UIActivityIndicatorView *) [dictionary valueForKey:kUserInfoActivityIndicatorKey];

   [activityIndicator stopAnimating];

   NSData *responseData = [aRequest responseData];
 UIImage * image = [[UIImage alloc] initWithData:responseData];

 imageView.image = image;
 [image release];
}

- (void)requestFailed:(ASIHTTPRequest *)request {
}

An ASIHTTPRequest is created and launched with the image as delegate. I think there is a risk if image is deallocated before ASIHTTPRequest return a result.

So, maybe adding a retain in setImageWithContentAtUrl: and adding a release in requestFinished: and requestFailed: but I'm not very confident.

How is it possible to do such things ?

Regards, Quentin

A: 

Quentin,

I regularly use ASIHTTPRequest for asynchronous calls, so I know where you're coming from here. Also, it's a pain to set up for the first time, but did you know that Three20 library's TTImageView (I think that's it) already does what you are trying to do? It will even cache the image locally so you don't have to load it every time. Anyway.

Your worry is correct: ASIHTTPRequest is a wrapper on an NSOperation object (it's actually a subclass), so the NSOperationQueue will retain ASIHTTPRequest as long as the request is active.

If your user changes the view (say, on a nav bar controller), which then deallocs your UIImageView, your code may crash when it tries to call back to the delegate. So, when you dealloc your image view, it's better to hold on to a reference to the request and then cancel it.

Rather than a category, this may be one of those times where subclassing is better - because you'd want to overwrite the dealloc method (this is how I've handled this issue).

First, add this property to your subclass:

@property (nonatomic, retain) ASIHTTPRequest *request;

Then add this line to your method so you can hold on to it:

self.request = request;

And finally, in your ASIHTTPRequest delegate methods, destroy the reference:

self.request = nil;

Then your dealloc could look like this:

- (void) dealloc
{
  if (self.request)
  {
    // Cancels the NSOperation so ASIHTTPRequest doesn't call back to this
    [self.request cancel];
  }
  [request release];
  [super dealloc]
}
phooze
Thank you for confirming my "fear". So I will subclass UIImageView.
Quentin