I wrote this (with concepts and some code taken from Lane Roathe's excellent UIImageView+Cache category) for an app I've been working on. It uses the ASIHTTPRequest classes as well, which are great. This could definitely be improved.. for example, by allowing requests to be canceled if no longer needed, or by utilizing the notification userInfo to allow for more precise UI updating.. but it's working well for my purposes.
@implementation ImageFetcher
#define MAX_CACHED_IMAGES 20
static NSMutableDictionary* cache = nil;
+ (void)asyncImageFetch:(UIImage**)anImagePtr withURL:(NSURL*)aUrl {
if(!cache) {
cache = [[NSMutableDictionary dictionaryWithCapacity:MAX_CACHED_IMAGES] retain];
}
UIImage* newImage = [cache objectForKey:aUrl.description];
if(!newImage) { // cache miss - doh!
ASIHTTPRequest *imageRequest = [ASIHTTPRequest requestWithURL:aUrl];
imageRequest.userInfo = [NSDictionary dictionaryWithObject:[NSValue valueWithPointer:anImagePtr] forKey:@"imagePtr"];
imageRequest.delegate = self;
[imageRequest setDidFinishSelector:@selector(didReceiveImage:)];
[imageRequest setDidFailSelector:@selector(didNotReceiveImage:)];
[imageRequest startAsynchronous];
}
else { // cache hit - good!
*anImagePtr = [newImage retain];
}
}
+ (void)didReceiveImage:(ASIHTTPRequest *)request {
NSLog(@"Image data received.");
UIImage **anImagePtr = [(NSValue*)[request.userInfo objectForKey:@"imagePtr"] pointerValue];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
UIImage *newImage = [[UIImage imageWithData:[request responseData]] retain];
if(!newImage) {
NSLog(@"UIImageView: LoadImage Failed");
}
else {
*anImagePtr = newImage;
// check to see if we should flush existing cached items before adding this new item
if( [cache count] >= MAX_CACHED_IMAGES)
[cache removeAllObjects];
[cache setValue:newImage forKey:[request url].description];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc postNotificationName: @"ImageDidLoad" object: self userInfo:request.userInfo];
}
[pool drain];
}
You call this code as follows:
[ImageFetcher asyncImageFetch:&icon withURL:url];
I'm also using notifications, for better or worse, to let any owners of the corresponding UIImage know when they should redisplay- in this case, it's in a tableView context:
- (void)viewDidLoad {
[super viewDidLoad];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:@selector(imageDidLoad:) name:@"ImageDidLoad" object:nil];
}
- (void)imageDidLoad:(NSNotification*)notif {
NSLog(@"Received icon load notification.");
// reload table view so that new image appears.. would be better if I could
// only reload the particular UIImageView that holds this image, oh well...
[self.tableView reloadData];
}
- (void)dealloc {
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc removeObserver:self];
// ...
}