简介
SDWebImage是一个具有缓存支持的异步图像下载器。为了方便,我们为UIImageView、UIButton、MKAnnotationView等UI元素添加了类别。
1.0版本2009年12月31日发布
1.0版本中代码非常简单,没有使用NSURLConnection
网络请求,只会从内存当中读取图片以及磁盘读写取文件,以及清除内存与磁盘缓存。
主要包括下列功能类:
UIImageView+WebCache 集成了图片下载
SDWebImageManager 下载管理器
SDWebImageDownloader 下载器(专门负责下载功能)
SDImageCache 负责缓存(内存缓存、磁盘缓存)
SDWebImageManagerDelegate 图片下载完成了
SDWebImageDownloaderDelegate 图片下载完成了
2.0版本2010年6月9日发布
2.0版本类结构上没有发生改变,最大改变增加了NSURLConnection
网络请求和下面的一些优化
1. SDWebImageDownloaderDelegate.h 新增错误回调
@protocol SDWebImageDownloaderDelegate
@optional
- (void)imageDownloader:(SDWebImageDownloader *)downloader didFinishWithImage:(UIImage *)image;
//-----------------------2.0版本更新-新增错误回调-----------------------
- (void)imageDownloader:(SDWebImageDownloader *)downloader didFailWithError:(NSError *)error;
//---------------------------------end------------------------------
@end
2. SDWebImageManager.m去掉downloadWithURL、cancelForDelegate、didFinishWithImage
方法的同步代码块
- (void)downloadWithURL:(NSURL *)url delegate:(id)delegate{
if (url == nil || [failedURLs containsObject:url]){
return;
}
SDWebImageDownloader *downloader = [downloaderForURL objectForKey:url];
if (!downloader){
downloader = [SDWebImageDownloader downloaderWithURL:url delegate:self];
[downloaderForURL setObject:downloader forKey:url];
}
//-----------------------2.0版本更新-去除@synchronized同步块-----------------------
[delegates addObject:delegate];
[downloaders addObject:downloader];
//-----------------------------------end-----------------------------
}
- (void)cancelForDelegate:(id)delegate{
//-----------------------2.0版本更新-去除@synchronized同步块-----------------------
NSUInteger idx = [delegates indexOfObjectIdenticalTo:delegate];
if (idx == NSNotFound){
return;
}
SDWebImageDownloader *downloader = [downloaders objectAtIndex:idx];
[delegates removeObjectAtIndex:idx];
[downloaders removeObjectAtIndex:idx];
if (![downloaders containsObject:downloader]){
[downloader cancel];
[downloaderForURL removeObjectForKey:downloader.url];
}
//-----------------------------------end-----------------------------
}
- (void)imageDownloader:(SDWebImageDownloader *)downloader didFinishWithImage:(UIImage *)image{
//-----------------------2.0版本更新-去除@synchronized同步块-----------------------
for (NSInteger idx = [downloaders count] - 1; idx >= 0; idx--){
SDWebImageDownloader *aDownloader = [downloaders objectAtIndex:idx];
if (aDownloader == downloader){
id delegate = [delegates objectAtIndex:idx];
if (image && [delegate respondsToSelector:@selector(webImageManager:didFinishWithImage:)]){
[delegate performSelector:@selector(webImageManager:didFinishWithImage:) withObject:self withObject:image];
}
[downloaders removeObjectAtIndex:idx];
[delegates removeObjectAtIndex:idx];
}
}
//-----------------------------------end-----------------------------
if (image){
[[SDImageCache sharedImageCache] storeImage:image forKey:[downloader.url absoluteString]];
} else {
[failedURLs addObject:downloader.url];
}
[downloaderForURL removeObjectForKey:downloader.url];
}
3. SDWebImageDownloader.h 的父类由NSOperation
更新为NSObject
,同时添加了NSURLConnection
成员变量。
//-----------------------2.0版本更新-新增网络请求,继承自NSObject-----------------------
@interface SDWebImageDownloader : NSObject{
@private
NSURL *url;
NSURLConnection *connection;
NSMutableData *imageData;
}
//-------------------------------end--------------------------------
@property (nonatomic, retain) NSURL *url;
@property (nonatomic, assign) id delegate;
+ (id)downloaderWithURL:(NSURL *)url delegate:(id)delegate;
//--------------2.0版本更新-新增网络请求,废弃setMaxConcurrentDownloads方法-----------------------
+ (void)setMaxConcurrentDownloads:(NSUInteger)max __attribute__((deprecated));
- (void)start;
- (void)cancel;
//-------------------------------end--------------------------------
@end
4. SDWebImageDownloader.m增加开始和取消网络请求方法start 、cancel
和网络请求回调方法didReceiveData、connectionDidFinishLoading、didFailWithError
//--------------------------2.0版本更新-网络请求--------------------------
@interface SDWebImageDownloader ()
@property (nonatomic, retain) NSURLConnection *connection;
@property (nonatomic, retain) NSMutableData *imageData;
@end
//--------------------------------end---------------------------------
@implementation SDWebImageDownloader
@synthesize url, delegate, connection, imageData;
//--------------------------2.0版本更新-去除NSOperationQueue--------------------------
+ (id)downloaderWithURL:(NSURL *)url delegate:(id)delegate{
SDWebImageDownloader *downloader = [[SDWebImageDownloader alloc] init];
downloader.url = url;
downloader.delegate = delegate;
[downloader start];
return downloader;
}
//--------------------------------end---------------------------------
+ (void)setMaxConcurrentDownloads:(NSUInteger)max{
// NOOP
}
//--------------------------2.0版本更新-网络请求--------------------------
- (void)start{
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:15];
self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[connection start];
if (connection){
self.imageData = [NSMutableData data];
} else {
if ([delegate respondsToSelector:@selector(imageDownloader:didFailWithError:)]){
[delegate performSelector:@selector(imageDownloader:didFailWithError:) withObject:self withObject:nil];
}
}
}
- (void)cancel{
if (connection){
[connection cancel];
self.connection = nil;
}
}
//--------------------------end--------------------------
//--------------------------2.0版本更新-请求回调--------------------------
- (void)connection:(NSURLConnection *)aConnection didReceiveData:(NSData *)data{
[imageData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection{
UIImage *image = [[UIImage alloc] initWithData:imageData];
self.imageData = nil;
self.connection = nil;
if ([delegate respondsToSelector:@selector(imageDownloader:didFinishWithImage:)]){
[delegate performSelector:@selector(imageDownloader:didFinishWithImage:) withObject:self withObject:image];
}
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
if ([delegate respondsToSelector:@selector(imageDownloader:didFailWithError:)]){
[delegate performSelector:@selector(imageDownloader:didFailWithError:) withObject:self withObject:error];
}
self.connection = nil;
self.imageData = nil;
}
//--------------------------end--------------------------
@end
5. SDImageCache.m新增后台通知UIApplicationDidEnterBackgroundNotification
,当进入后台模式清空内存缓存,优化内存存储。
- (instancetype)init{
self = [super init];
if (self) {
//内存缓存
memCache = [[NSMutableDictionary alloc] init];
//磁盘缓存
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
//磁盘目录
diskCachePath = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"ImageCache"];
//创建目录
if (![[NSFileManager defaultManager] fileExistsAtPath:diskCachePath]){
[[NSFileManager defaultManager] createDirectoryAtPath:diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
}
//初始化缓存队列
cacheInQueue = [[NSOperationQueue alloc] init];
cacheInQueue.maxConcurrentOperationCount = 2;
//订阅应用程序事件
//应用程序终止,里面回调didReceiveMemoryWarning方法,清空内存
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didReceiveMemoryWarning:)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
//应用程序终止,里面回调willTerminate方法,清空磁盘
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(willTerminate)
name:UIApplicationWillTerminateNotification
object:nil];
//-----------------------2.0版本更新-新增后台通知-----------------------
UIDevice *device = [UIDevice currentDevice];
if ([device respondsToSelector:@selector(isMultitaskingSupported)] && device.multitaskingSupported){
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(clearMemory)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
}
//--------------------------------end--------------------------------
}
return self;
}
2.1版本2010年6月12日发布
1. UIImageView+WebCache 新增取消图片加载
@interface UIImageView (WebCache)
- (void)setImageWithURL:(NSURL *)url;
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder;
//----------------------2.1版本更新-新增-取消图片加载--------------------
- (void)cancelCurrentImageLoad;
//------------------------------end----------------------------------
@end
//-----------------------2.1版本更新-取消图片加载-----------------------
- (void)cancelCurrentImageLoad{
[[SDWebImageManager sharedManager] cancelForDelegate:self];
}
//------------------------------end----------------------------------
2. SDImageCache 缓存器做了优化,兼容iPhone4增加了宏定义
//-----------------------2.1版本更新-系统版本兼容宏定义-----------------------
#ifdef __IPHONE_4_0
UIDevice *device = [UIDevice currentDevice];
if ([device respondsToSelector:@selector(isMultitaskingSupported)] && device.multitaskingSupported){
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(clearMemory)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
}
#endif
//--------------------------------end--------------------------------
}
2.2版本2010年8月29日发布
更新说明:如果图片格式是PNG或GIF,当存储到磁盘进行缓存时候,不要将图像转换为JPEG,因为他能够节省CPU和内存以及alpha通道/图像的清晰度。
1. SDWebImageDownloader 新增加了属性,在主线程下载和优化了回调处理
@property (nonatomic, retain) NSURL *url;
@property (nonatomic, assign) id delegate;
//-------------2.2版本更新-新增-新增属性-------------------
@property (nonatomic, retain) NSMutableData *imageData;
//--------------------------end-------------------------
+ (id)downloaderWithURL:(NSURL *)url delegate:(id)delegate{
SDWebImageDownloader *downloader = [[SDWebImageDownloader alloc] init];
downloader.url = url;
downloader.delegate = delegate;
//-------------2.2版本更新-在主线程下载-------------------
[downloader performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:YES];
//--------------------------end-------------------------
return downloader;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection{
//------------------2.2版本更新-优化----------------------
self.connection = nil;
//下载器下载完成
if ([delegate respondsToSelector:@selector(imageDownloaderDidFinish:)]){
[delegate performSelector:@selector(imageDownloaderDidFinish:) withObject:self];
}
if ([delegate respondsToSelector:@selector(imageDownloader:didFinishWithImage:)]){
UIImage *image = [[UIImage alloc] initWithData:imageData];
[delegate performSelector:@selector(imageDownloader:didFinishWithImage:) withObject:self withObject:image];
}
//--------------------------end--------------------------
}
2. 缓存器->SDImageCache 新增了属性和方法重载,增加了二进制NSData数据存储到磁盘
@interface SDImageCache : NSObject{
NSMutableDictionary *memCache;
NSString *diskCachePath;
NSOperationQueue *cacheInQueue;
//-------------2.2版本更新-新增-方法重载-功能抽象-------------------
NSMutableDictionary *storeDataQueue;
//--------------------------------end------------------------------
}
//-------------2.2版本更新-新增-方法重载-功能抽象-------------------
- (void)storeImage:(UIImage *)image imageData:(NSData *)data forKey:(NSString *)key toDisk:(BOOL)toDisk;
//-------------------------------end---------------------------
//-------------2.2版本更新-新增-方法重载-功能抽象-------------------
- (void)storeImage:(UIImage *)image forKey:(NSString *)key{
[self storeImage:image imageData:nil forKey:key toDisk:YES];
}
- (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk{
[self storeImage:image imageData:nil forKey:key toDisk:toDisk];
}
- (void)storeImage:(UIImage *)image imageData:(NSData *)data forKey:(NSString *)key toDisk:(BOOL)toDisk{
if (!image || !key){
return;
}
if (toDisk && !data){
return;
}
[memCache setObject:image forKey:key];
if (toDisk){
[storeDataQueue setObject:data forKey:key];
[cacheInQueue addOperation:[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(storeKeyToDisk:) object:key]];
}
}
//-------------------------------end---------------------------
- (void)storeKeyToDisk:(NSString *)key{
//-------------2.2版本更新-磁盘缓存-------------------
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSData *data = [storeDataQueue objectForKey:key];
if (data){
//存储二进制->NSData
[fileManager createFileAtPath:[self cachePathForKey:key] contents:data attributes:nil];
@synchronized(storeDataQueue){
[storeDataQueue removeObjectForKey:key];
}
//----------------------end------------------------
} else {
//存储图片->UIImage
UIImage *image = [self imageFromKey:key fromDisk:YES];
if (image){
[fileManager createFileAtPath:[self cachePathForKey:key] contents:UIImageJPEGRepresentation(image, (CGFloat)1.0) attributes:nil];
}
}
}
3. SDWebImageDownloaderDelegate 新增了回调imageDownloaderDidFinish
针对于imageData
它而设计,后面版本迭代会更新具体实现暂时没有。
//-----------------------2.2版本更新-下载完成回调-----------------------
- (void)imageDownloaderDidFinish:(SDWebImageDownloader *)downloader;
//---------------------------------end------------------------------
2.3版本2010年9月17日发布
1. SDImageCacheDelegate 新增该代理类
//-------------------2.3版本更新-缓存回调-------------------
@class SDImageCache;
@protocol SDImageCacheDelegate
@optional
//缓存过程中有图片,缓存成功
- (void)imageCache:(SDImageCache *)imageCache didFindImage:(UIImage *)image forKey:(NSString *)key userInfo:(NSDictionary *)info;
//缓存过程中没有图片,缓存失败了
- (void)imageCache:(SDImageCache *)imageCache didNotFindImageForKey:(NSString *)key userInfo:(NSDictionary *)info;
@end
//---------------------------end-------------------------
2. UIImageView + WebCache 抽象缓存逻辑到管理器中
//-------------------2.3版本更新-优化处理-------------------
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder{
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager cancelForDelegate:self];
self.image = placeholder;
if (url){
[manager downloadWithURL:url delegate:self];
}
}
//---------------------------end-------------------------
3. SDWebImageManager 实现SDImageCacheDelegate
协议
缓存代理,异步检查磁盘上的缓存,这样我们就不会阻塞主线程缓存代理回调实现。
- (void)downloadWithURL:(NSURL *)url delegate:(id)delegate{
if (!url || !delegate || [failedURLs containsObject:url]){
return;
}
//-------------------2.3版本更新-避免阻塞主线程-------------------
//检查磁盘上的缓存异步,这样我们就不会阻塞主线程
NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:delegate, @"delegate", url, @"url", nil];
[[SDImageCache sharedImageCache] queryDiskCacheForKey:[url absoluteString] delegate:self userInfo:info];
//---------------------------end-------------------------
}
//--------------------2.3版本更新--实现协议SDImageCacheDelegate功能----------
- (void)imageCache:(SDImageCache *)imageCache didFindImage:(UIImage *)image forKey:(NSString *)key userInfo:(NSDictionary *)info{
id delegate = [info objectForKey:@"delegate"];
if ([delegate respondsToSelector:@selector(webImageManager:didFinishWithImage:)]){
[delegate performSelector:@selector(webImageManager:didFinishWithImage:) withObject:self withObject:image];
}
}
- (void)imageCache:(SDImageCache *)imageCache didNotFindImageForKey:(NSString *)key userInfo:(NSDictionary *)info{
NSURL *url = [info objectForKey:@"url"];
id delegate = [info objectForKey:@"delegate"];
//为相同的URL共享相同的下载加载程序,所以我们不会多次下载相同的URL
SDWebImageDownloader *downloader = [downloaderForURL objectForKey:url];
if (!downloader){
downloader = [SDWebImageDownloader downloaderWithURL:url delegate:self];
[downloaderForURL setObject:downloader forKey:url];
}
[delegates addObject:delegate];
[downloaders addObject:downloader];
}
//------------------------------------end---------------------------------
3. SDImageCache
- 新增了属性
cacheOutQueue
避免阻塞主线程, - 新增异步读取缓存方法
queryDiskCacheForKey
两个缓存:内存缓存、磁盘缓存读取是耗时操作 - 新增了
notifyDelegate
方法通知所有的缓存代理 - 新增了
queryDiskCacheOperation
方法2.3版本网络请求还没有完善
@interface SDImageCache : NSObject{
NSMutableDictionary *memCache;
NSString *diskCachePath;
NSOperationQueue *cacheInQueue;
//-------------------2.3版本更新-避免阻塞主线程--------------------
NSOperationQueue *cacheOutQueue;
//--------------------------------end--------------------------
}
- (UIImage *)imageFromKey:(NSString *)key;
- (UIImage *)imageFromKey:(NSString *)key fromDisk:(BOOL)fromDisk;
//---------------2.3版本更新-新增功能->异步方法--------------------
- (void)queryDiskCacheForKey:(NSString *)key delegate:(id)delegate userInfo:(NSDictionary *)info;
//-------------------------------end---------------------------
@end
//---------------2.3版本更新-新增功能->异步方法--------------------
- (void)notifyDelegate:(NSDictionary *)arguments{
NSString *key = [arguments objectForKey:@"key"];
id delegate = [arguments objectForKey:@"delegate"];
NSDictionary *info = [arguments objectForKey:@"userInfo"];
UIImage *image = [arguments objectForKey:@"image"];
if (image){
[memCache setObject:image forKey:key];
if ([delegate respondsToSelector:@selector(imageCache:didFindImage:forKey:userInfo:)]){
[delegate imageCache:self didFindImage:image forKey:key userInfo:info];
}
} else {
if ([delegate respondsToSelector:@selector(imageCache:didNotFindImageForKey:userInfo:)]){
[delegate imageCache:self didNotFindImageForKey:key userInfo:info];
}
}
}
- (void)queryDiskCacheOperation:(NSDictionary *)arguments{
NSString *key = [arguments objectForKey:@"key"];
NSMutableDictionary *mutableArguments = [arguments mutableCopy];
UIImage *image = [[UIImage alloc] initWithContentsOfFile:[self cachePathForKey:key]];
if (image){
[mutableArguments setObject:image forKey:@"image"];
}
[self performSelectorOnMainThread:@selector(notifyDelegate:) withObject:mutableArguments waitUntilDone:NO];
}
- (void)queryDiskCacheForKey:(NSString *)key delegate:(id )delegate userInfo:(NSDictionary *)info{
if (!delegate){
return;
}
if (!key){
if ([delegate respondsToSelector:@selector(imageCache:didNotFindImageForKey:userInfo:)]){
[delegate imageCache:self didNotFindImageForKey:key userInfo:info];
}
return;
}
//首先检查内存缓存…
UIImage *image = [memCache objectForKey:key];
if (image){
//从缓存中读取到了图片
//立即通知delegate,不需要去异步
if ([delegate respondsToSelector:@selector(imageCache:didFindImage:forKey:userInfo:)]){
[delegate imageCache:self didFindImage:image forKey:key userInfo:info];
}
return;
}
//缓存中没有
NSMutableDictionary *arguments = [NSMutableDictionary dictionaryWithCapacity:3];
[arguments setObject:key forKey:@"key"];
[arguments setObject:delegate forKey:@"delegate"];
if (info){
[arguments setObject:info forKey:@"userInfo"];
}
[cacheOutQueue addOperation:[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(queryDiskCacheOperation:) object:arguments]];
}
//------------------------end------------------------