Remove the dirty hack of storing the helper into the first subview of the UIImageView and prevent duplicate downloads of the same URL
The helper (now called manager) is now handling the mapping between the UIImageView and its downloader. This way we don't polute the UIImageView, and don't remove its capability to have subviews. This change removes the automatic handling of image placeholder. The placeholder image can be passed as second argument of setImageWithURL:placeholderImage: The manager now handle duplicate downloads for the same URL gracefuly by sharing the same downloader for all requestors. Finaly, the manager handles URLs which can't create an image (HTTP error or invalid format) by flagging them so it won't retry to download them again and again.
This commit is contained in:
parent
972c304957
commit
2fa0626aaa
68
README.md
68
README.md
|
@ -5,9 +5,12 @@ This library provides a category for UIImageVIew with support for remote images
|
|||
|
||||
It provides:
|
||||
|
||||
- Drop-in replacement to UIImageView
|
||||
- Asynchronous image downloader
|
||||
- Asynchronous memory + disk image caching with automatic cache expiration handling
|
||||
- An UIImageView category adding web image and cache management to the Cocoa Touch framework
|
||||
- An asynchronous image downloader using threads (NSOperation)
|
||||
- An asynchronous memory + disk image caching with automatic cache expiration handling
|
||||
- A garantie that the same URL won't be downloaded several times
|
||||
- A garantie that bogus URLs won't be retried again and again
|
||||
- Performances!
|
||||
|
||||
Motivation
|
||||
----------
|
||||
|
@ -59,11 +62,9 @@ How To Use It
|
|||
|
||||
### Using UIImageView+WebCache category with UITableView
|
||||
|
||||
Just #import the UIImageView+WebCache.h header, and call the setImageWithURL: method from the
|
||||
tableView:cellForRowAtIndexPath: UITableViewDataSource method. Everything will be handled for you,
|
||||
from parallel downloads to caching management. If you assigned an image to the view (via the
|
||||
`images` property), this image will be used as a placeholder, waiting for the web image to be
|
||||
loaded.
|
||||
Just #import the UIImageView+WebCache.h header, and call the setImageWithURL:placeholderImage:
|
||||
method from the tableView:cellForRowAtIndexPath: UITableViewDataSource method. Everything will be
|
||||
handled for you, from parallel downloads to caching management.
|
||||
|
||||
#import "UIImageView+WebCache.h"
|
||||
|
||||
|
@ -79,31 +80,58 @@ loaded.
|
|||
{
|
||||
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
|
||||
reuseIdentifier:MyIdentifier] autorelease];
|
||||
|
||||
// Here we set the placeholder image
|
||||
cell.imageView.image = [UIImage imageNamed:@"placeholder.png"];
|
||||
}
|
||||
|
||||
// Here we use the new provided setImageWithURL: method to load the web image
|
||||
[cell.imageView setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"]];
|
||||
[cell.imageView setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"]
|
||||
placeholderImage:[UIImage imageNamed:@"placeholder.png"]];
|
||||
|
||||
cell.textLabel.text = @"My Text";
|
||||
return cell;
|
||||
}
|
||||
|
||||
### Asynchronous Image Downloader
|
||||
### Using SDWebImageManager
|
||||
|
||||
The SDWebImageManager is the class behind the UIImageView+WebCache category. It ties the
|
||||
asynchronous downloader with the image cache store. You can use this classe directly to benefits
|
||||
from web image downloading with caching in another context than a UIView (ie: with Cocos).
|
||||
|
||||
Here is a simple example of how to use SDWebImageManager:
|
||||
|
||||
SDWebImageManager *manager = [SDWebImageManager sharedManager];
|
||||
|
||||
UIImage *cachedImage = [manager imageWithURL:url];
|
||||
|
||||
if (cachedImage)
|
||||
{
|
||||
// Use the cached image immediatly
|
||||
}
|
||||
else
|
||||
{
|
||||
// Start an async download
|
||||
[manager downloadWithURL:url delegate:self];
|
||||
}
|
||||
|
||||
Your class will have to implement the SDWebImageManagerDelegate protocol, and to implement the
|
||||
imageHelper:didFinishWithImage: method from this protocol:
|
||||
|
||||
- (void)imageHelper:(SDWebImageManager *)imageHelper didFinishWithImage:(UIImage *)image
|
||||
{
|
||||
// Do something with the downloaded image
|
||||
}
|
||||
|
||||
### Using Asynchronous Image Downloader Independently
|
||||
|
||||
It is possible to use the NSOperation based image downloader independently. Just create an instance
|
||||
of SDWebImageDownloader using its convenience constructor downloaderWithURL:target:action:.
|
||||
|
||||
downloader = [SDWebImageDownloader downloaderWithURL:url
|
||||
target:self
|
||||
action:@selector(downloadFinishedWithImage:)];
|
||||
downloader = [SDWebImageDownloader downloaderWithURL:url delegate:self];
|
||||
|
||||
The download will by queued immediately and the downloadFinishedWithImage: method will be called as
|
||||
soon as the download of image will be completed (prepare not to be called from the main thread).
|
||||
The download will by queued immediately and the imageDownloader:didFinishWithImage: method from the
|
||||
SDWebImageDownloaderDelegate protocol will be called as soon as the download of image is completed
|
||||
(prepare not to be called from the main thread).
|
||||
|
||||
### Asynchronous Image Caching
|
||||
### Using Asynchronous Image Caching Independently
|
||||
|
||||
It is also possible to use the NSOperation based image cache store independently. SDImageCache
|
||||
maintains a memory cache and an optional disk cache. Disk cache write operations are performed
|
||||
|
@ -134,10 +162,8 @@ third argument.
|
|||
Future Enhancements
|
||||
-------------------
|
||||
|
||||
- Easy way to use it with default UITableView styles without requiring to create a custom UITableViewCell
|
||||
- LRU memory cache cleanup instead of reset on memory warning
|
||||
|
||||
|
||||
[Dailymotion]: http://www.dailymotion.com
|
||||
[Fraggle]: http://fraggle.squarespace.com
|
||||
[Urban Rivals]: http://fraggle.squarespace.com/blog/2009/9/15/almost-done-here-is-urban-rivals-iphone-trailer.html
|
||||
|
|
|
@ -7,19 +7,18 @@
|
|||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "SDWebImageDownloaderDelegate.h"
|
||||
|
||||
@interface SDWebImageDownloader : NSOperation
|
||||
{
|
||||
NSURL *url;
|
||||
id target;
|
||||
SEL action;
|
||||
id<SDWebImageDownloaderDelegate> delegate;
|
||||
}
|
||||
|
||||
@property (retain) NSURL *url;
|
||||
@property (assign) id target;
|
||||
@property (assign) SEL action;
|
||||
@property (assign) id<SDWebImageDownloaderDelegate> delegate;
|
||||
|
||||
+ (id)downloaderWithURL:(NSURL *)url target:(id)target action:(SEL)action;
|
||||
+ (id)downloaderWithURL:(NSURL *)url delegate:(id<SDWebImageDownloaderDelegate>)delegate;
|
||||
+ (void)setMaxConcurrentDownloads:(NSUInteger)max;
|
||||
|
||||
@end
|
||||
|
|
|
@ -12,7 +12,7 @@ static NSOperationQueue *downloadQueue;
|
|||
|
||||
@implementation SDWebImageDownloader
|
||||
|
||||
@synthesize url, target, action;
|
||||
@synthesize url, delegate;
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
|
@ -20,12 +20,11 @@ static NSOperationQueue *downloadQueue;
|
|||
[super dealloc];
|
||||
}
|
||||
|
||||
+ (id)downloaderWithURL:(NSURL *)url target:(id)target action:(SEL)action
|
||||
+ (id)downloaderWithURL:(NSURL *)url delegate:(id<SDWebImageDownloaderDelegate>)delegate
|
||||
{
|
||||
SDWebImageDownloader *downloader = [[[SDWebImageDownloader alloc] init] autorelease];
|
||||
downloader.url = url;
|
||||
downloader.target = target;
|
||||
downloader.action = action;
|
||||
downloader.delegate = delegate;
|
||||
|
||||
if (downloadQueue == nil)
|
||||
{
|
||||
|
@ -54,9 +53,9 @@ static NSOperationQueue *downloadQueue;
|
|||
|
||||
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
|
||||
|
||||
if (!self.isCancelled)
|
||||
if (!self.isCancelled && [delegate respondsToSelector:@selector(imageDownloader:didFinishWithImage:)])
|
||||
{
|
||||
[target performSelector:action withObject:image];
|
||||
[delegate performSelector:@selector(imageDownloader:didFinishWithImage:) withObject:self withObject:image];
|
||||
}
|
||||
|
||||
[pool release];
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* This file is part of the SDWebImage package.
|
||||
* (c) Olivier Poitrey <rs@dailymotion.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
@class SDWebImageDownloader;
|
||||
|
||||
@protocol SDWebImageDownloaderDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
|
||||
- (void)imageDownloader:(SDWebImageDownloader *)downloader didFinishWithImage:(UIImage *)image;
|
||||
|
||||
@end
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* This file is part of the SDWebImage package.
|
||||
* (c) Olivier Poitrey <rs@dailymotion.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
@class SDWebImageManager;
|
||||
|
||||
@protocol SDWebImageManagerDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
|
||||
- (void)imageHelper:(SDWebImageManager *)imageHelper didFinishWithImage:(UIImage *)image;
|
||||
|
||||
@end
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* This file is part of the SDWebImage package.
|
||||
* (c) Olivier Poitrey <rs@dailymotion.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "SDWebImageDownloaderDelegate.h"
|
||||
#import "SDWebImageHelperDelegate.h"
|
||||
|
||||
@interface SDWebImageManager : NSObject <SDWebImageDownloaderDelegate>
|
||||
{
|
||||
NSMutableArray *delegates;
|
||||
NSMutableArray *downloaders;
|
||||
NSMutableDictionary *downloaderForURL;
|
||||
NSMutableArray *failedURLs;
|
||||
}
|
||||
|
||||
+ (id)sharedManager;
|
||||
- (UIImage *)imageWithURL:(NSURL *)url;
|
||||
- (void)downloadWithURL:(NSURL *)url delegate:(id<SDWebImageManagerDelegate>)delegate;
|
||||
- (void)cancelForDelegate:(id<SDWebImageManagerDelegate>)delegate;
|
||||
|
||||
@end
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* This file is part of the SDWebImage package.
|
||||
* (c) Olivier Poitrey <rs@dailymotion.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#import "SDWebImageManager.h"
|
||||
#import "SDImageCache.h"
|
||||
#import "SDWebImageDownloader.h"
|
||||
|
||||
static SDWebImageManager *instance;
|
||||
|
||||
@implementation SDWebImageManager
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
delegates = [[NSMutableArray alloc] init];
|
||||
downloaders = [[NSMutableArray alloc] init];
|
||||
downloaderForURL = [[NSMutableDictionary alloc] init];
|
||||
failedURLs = [[NSMutableArray alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
[delegates release];
|
||||
[downloaders release];
|
||||
[downloaderForURL release];
|
||||
[failedURLs release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
|
||||
+ (id)sharedManager
|
||||
{
|
||||
if (instance == nil)
|
||||
{
|
||||
instance = [[SDWebImageManager alloc] init];
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
- (UIImage *)imageWithURL:(NSURL *)url
|
||||
{
|
||||
return [[SDImageCache sharedImageCache] imageFromKey:[url absoluteString]];
|
||||
}
|
||||
|
||||
- (void)downloadWithURL:(NSURL *)url delegate:(id<SDWebImageManagerDelegate>)delegate
|
||||
{
|
||||
if ([failedURLs containsObject:url])
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Share the same downloader for identical URLs so we don't download the same URL several times
|
||||
SDWebImageDownloader *downloader = [downloaderForURL objectForKey:url];
|
||||
|
||||
if (!downloader)
|
||||
{
|
||||
downloader = [SDWebImageDownloader downloaderWithURL:url delegate:self];
|
||||
[downloaderForURL setObject:downloader forKey:url];
|
||||
}
|
||||
|
||||
@synchronized(self)
|
||||
{
|
||||
[delegates addObject:delegate];
|
||||
[downloaders addObject:downloader];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)cancelForDelegate:(id<SDWebImageManagerDelegate>)delegate
|
||||
{
|
||||
@synchronized(self)
|
||||
{
|
||||
NSUInteger index = [delegates indexOfObjectIdenticalTo:delegate];
|
||||
|
||||
if (index == NSNotFound)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SDWebImageDownloader *downloader = [[downloaders objectAtIndex:index] retain];
|
||||
|
||||
[delegates removeObjectAtIndex:index];
|
||||
[downloaders removeObjectAtIndex:index];
|
||||
|
||||
if (![downloaders containsObject:downloader])
|
||||
{
|
||||
NSLog(@"cancel download");
|
||||
// No more delegate are waiting for this download, cancel it
|
||||
[downloader cancel];
|
||||
[downloaderForURL removeObjectForKey:downloader.url];
|
||||
}
|
||||
|
||||
[downloader release];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)imageDownloader:(SDWebImageDownloader *)downloader didFinishWithImage:(UIImage *)image
|
||||
{
|
||||
[downloader retain];
|
||||
|
||||
@synchronized(self)
|
||||
{
|
||||
// Notify all the delegates with this downloader
|
||||
for (NSInteger index = [downloaders count] - 1; index >= 0; index--)
|
||||
{
|
||||
SDWebImageDownloader *aDownloader = [downloaders objectAtIndex:index];
|
||||
if (aDownloader == downloader)
|
||||
{
|
||||
id<SDWebImageManagerDelegate> delegate = [delegates objectAtIndex:index];
|
||||
|
||||
if (image && [delegate respondsToSelector:@selector(imageHelper:didFinishWithImage:)])
|
||||
{
|
||||
[delegate performSelector:@selector(imageHelper:didFinishWithImage:) withObject:self withObject:image];
|
||||
}
|
||||
|
||||
[downloaders removeObjectAtIndex:index];
|
||||
[delegates removeObjectAtIndex:index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (image)
|
||||
{
|
||||
// Store the image in the cache
|
||||
[[SDImageCache sharedImageCache] storeImage:image forKey:[downloader.url absoluteString]];
|
||||
}
|
||||
else
|
||||
{
|
||||
// The image can't be downloaded from this URL, mark the URL as failed so we won't try and fail again and again
|
||||
[failedURLs addObject:downloader.url];
|
||||
}
|
||||
|
||||
|
||||
// Release the downloader
|
||||
[downloaderForURL removeObjectForKey:downloader.url];
|
||||
[downloader release];
|
||||
}
|
||||
|
||||
|
||||
@end
|
|
@ -7,9 +7,11 @@
|
|||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "SDWebImageHelperDelegate.h"
|
||||
|
||||
@interface UIImageView (WebCache)
|
||||
@interface UIImageView (WebCache) <SDWebImageManagerDelegate>
|
||||
|
||||
- (void)setImageWithURL:(NSURL *)url;
|
||||
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder;
|
||||
|
||||
@end
|
||||
|
|
|
@ -7,48 +7,42 @@
|
|||
*/
|
||||
|
||||
#import "UIImageView+WebCache.h"
|
||||
#import "UIImageViewHelper.h"
|
||||
#import "SDWebImageManager.h"
|
||||
|
||||
@implementation UIImageView (WebCache)
|
||||
|
||||
- (void)setImageWithURL:(NSURL *)url
|
||||
{
|
||||
UIImageViewHelper *helper = nil;
|
||||
[self setImageWithURL:url placeholderImage:nil];
|
||||
}
|
||||
|
||||
if ([self.subviews count] > 0)
|
||||
{
|
||||
helper = [self.subviews objectAtIndex:0];
|
||||
}
|
||||
|
||||
if (helper == nil)
|
||||
{
|
||||
helper = [[[UIImageViewHelper alloc] initWithDelegate:self] autorelease];
|
||||
[self addSubview:helper];
|
||||
}
|
||||
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder
|
||||
{
|
||||
SDWebImageManager *manager = [SDWebImageManager sharedManager];
|
||||
|
||||
// Remove in progress downloader from queue
|
||||
[helper cancel];
|
||||
|
||||
// Save the placeholder image in order to re-apply it when view is reused
|
||||
if (helper.placeHolderImage == nil)
|
||||
{
|
||||
helper.placeHolderImage = self.image;
|
||||
}
|
||||
else
|
||||
{
|
||||
self.image = helper.placeHolderImage;
|
||||
}
|
||||
|
||||
UIImage *cachedImage = [helper imageWithURL:url];
|
||||
|
||||
[manager cancelForDelegate:self];
|
||||
|
||||
UIImage *cachedImage = [manager imageWithURL:url];
|
||||
|
||||
if (cachedImage)
|
||||
{
|
||||
self.image = cachedImage;
|
||||
}
|
||||
else
|
||||
{
|
||||
[helper downloadWithURL:url];
|
||||
if (placeholder)
|
||||
{
|
||||
self.image = placeholder;
|
||||
}
|
||||
|
||||
[manager downloadWithURL:url delegate:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)imageHelper:(SDWebImageManager *)imageHelper didFinishWithImage:(UIImage *)image
|
||||
{
|
||||
self.image = image;
|
||||
}
|
||||
|
||||
@end
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* This file is part of the SDWebImage package.
|
||||
* (c) Olivier Poitrey <rs@dailymotion.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "SDWebImageDownloader.h"
|
||||
|
||||
@interface UIImageViewHelper : UIView
|
||||
{
|
||||
UIImageView *delegate;
|
||||
SDWebImageDownloader *downloader;
|
||||
UIImage *placeHolderImage;
|
||||
}
|
||||
|
||||
@property (nonatomic, retain) UIImage *placeHolderImage;
|
||||
|
||||
- (id)initWithDelegate:(UIImageView *)aDelegate;
|
||||
- (UIImage *)imageWithURL:(NSURL *)url;
|
||||
- (void)downloadWithURL:(NSURL *)url;
|
||||
- (void)cancel;
|
||||
|
||||
@end
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
* This file is part of the SDWebImage package.
|
||||
* (c) Olivier Poitrey <rs@dailymotion.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#import "UIImageViewHelper.h"
|
||||
#import "SDImageCache.h"
|
||||
|
||||
@implementation UIImageViewHelper
|
||||
|
||||
@synthesize placeHolderImage;
|
||||
|
||||
- (id)initWithDelegate:(UIImageView *)aDelegate
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
delegate = aDelegate;
|
||||
self.hidden = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (UIImage *)imageWithURL:(NSURL *)url
|
||||
{
|
||||
return [[SDImageCache sharedImageCache] imageFromKey:[url absoluteString]];
|
||||
}
|
||||
|
||||
- (void)downloadWithURL:(NSURL *)url
|
||||
{
|
||||
downloader = [[SDWebImageDownloader downloaderWithURL:url target:self action:@selector(downloadFinishedWithImage:)] retain];
|
||||
}
|
||||
|
||||
- (void)cancel
|
||||
{
|
||||
[downloader cancel];
|
||||
[downloader release];
|
||||
downloader = nil;
|
||||
}
|
||||
|
||||
- (void)downloadFinishedWithImage:(UIImage *)anImage
|
||||
{
|
||||
// Apply image to the underlaying UIImageView
|
||||
delegate.image = anImage;
|
||||
|
||||
// Store the image in the cache
|
||||
[[SDImageCache sharedImageCache] storeImage:anImage forKey:[downloader.url absoluteString]];
|
||||
|
||||
// Free the downloader
|
||||
[downloader release];
|
||||
downloader = nil;
|
||||
}
|
||||
|
||||
@end
|
Loading…
Reference in New Issue