SDWebImage源代码阅读(一)
border="0" width="530" height="100" src="http://music.163.com/outchain/player?type=2&id=29947420&auto=1&height=66">首先看了张朝龙的博客关于SDwebImage的阅读,然后自己也去看了一下SDWebImage的源代码,以下结合多个方面给出自己的理解
为什么我们要使用SDWebImage去缓存图片:
- 流量很贵
- APP不缓存图片,每次都要重新去下载图片
- 所以这样APP就变得很费钱
- 花钱的东西在天朝是不招人喜欢的,天朝子民都喜欢破解,所以我们需要尽量节约他们的流量
- 使用SDWebImage可以很好地缓存图片,这样就做到APP不会被天朝的子民讨厌的第一步了
怎么用SDWebImage(GitHub上的,自己的理解和翻译):
首先引用:
#import
然后:
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:@"placeholder.png"]];
带闭包的:
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:@"placeholder.png"]
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
... completion code here ...
}];
注意:如果你的图像加载在完成前取消,无论成功还是是失败都会调用这个闭包
SDWwebImageManager:
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadImageWithURL:imageURL
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
// progression tracking code
}
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (image) {
// do something with image
}
}];
单独使用异步图片下载:
SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader];
[downloader downloadImageWithURL:imageURL
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
// progression tracking code
}
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
if (image && finished) {
// do something with image
}
}];
单独使用异步图片缓存
SDImageCache使用内存或者磁盘保存图片,磁盘高速缓存的读写是异步操作,所以不会对界面造成延迟
用SDImageCache通过key来获取图片,如果图片为nil,表示目前没有该图片的缓存
SDImageCache *imageCache = [[SDImageCache alloc] initWithNamespace:@"myNamespace"];
[imageCache queryDiskCacheForKey:myCacheKey done:^(UIImage *image) {
// image is not nil if image was found
}];
如果你只想在内存中查找,可以使用方法imageFromMemoryCacheForKey:
用SDImageCache通过key来保存图片
[[SDImageCache sharedImageCache] storeImage:myImage forKey:myCacheKey];
如果你只想把图片储存在内存中可以使用方法storeImage:forKey:toDisk:
并设置toDisk的参数来完成
使用缓存关键词过滤
有时你也许不想使用图像URL作为缓存键,因为URL可能是动态的(i.e.: for access control purpose)。SDWebImageManager provides a way to set a cache key filter that takes the NSURL as input, and output a cache key NSString(SDWebImageManager提供给我们一个方法去设置一个缓存过滤器,我们将URL给它,然后它换给我们是key的字符串(URL)
)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
SDWebImageManager.sharedManager.cacheKeyFilter = ^(NSURL *url) {
url = [[NSURL alloc] initWithScheme:url.scheme host:url.host path:url.path];
return [url absoluteString];
};
// Your app init code...
return YES;
}
源码阅读:
我们使用频率最高的,也是简单便捷的sd_setImageWithURL:···
函数,所以我们从这里看
UIImageView+WebCache:
只要是sd_setImageWithURL:
无论之后的参数是什么都会执行
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
}
参数的省略都是对此函数的参数的变化,这里是显示了SDwebImage库DRY做的非常好,值得去学习的不只是他的代码,还有他的一些规范。
先看该函数的参数:
url:大家都知道的
placeholder:相当于textfield那种没有文字输出的状态的背景文字,这里是图片没有加载完毕是的背景图片
options:这里是一些下载图片的配置
- SDWebImageRetryFailed 失败重试,取消黑名单,默认情况:失败后将网址列入黑名单,不再加载
- SDWebImageLowPriority 低优先级,默认情况,图像下载在用户界面交互过程中仍然继续,此标志禁用此功能,(e:UIScrollView滑动的情况下,下载会延迟)
- SDWebImageCacheMemoryOnly 只用内存缓存图片,此标志禁用磁盘缓存
- SDWebImageProgressiveDownload 此标志可进行渐进式下载,图像显示在下载过程中逐步显示像一个浏览器一样。默认情况下,图像只显示一次完全下载。
- SDWebImageRefreshCached 刷新缓存。即使图像已经被缓存了,以HTTP响应缓存控制为主,如果需要的话,从远程位置刷新图像。磁盘缓存将被NSURLCache操作而不是SDWebImage,所以会导致轻微的性能退化。此选项有助于处理在同一个请求的后面改变的图像,例如脸谱网图形的应用程序的配置文件。如果一个缓存的图像被刷新,完成块被执行一次与缓存的图像,并再次执行与最终图像。如果你不能使你的URL是静态的,请使用此标志。
- SDWebImageContinueInBackground 在后台继续执行。在IOS4以上的系统,如果应用程序在后台执行时想继续下载图片,我们需要向系统请求。在后台完成请求,需要花更多的时间,如果后台任务过期,操作将被取消
- SDWebImageHandleCookies 设置此项,我们会将cookies存储在NSHTTPCookieStore里,相当于设置
NSMutableURLRequest.HTTPShouldHandleCookies = YES;
- SDWebImageAllowInvalidSSLCertificates 使允许不受信任的SSL证书,用于测试目的,在生产中谨慎使用。
- SDWebImageHighPriority 默认情况下,图片的加载是在队列中的,此标志将其移动到队列的前面
- SDWebImageDelayPlaceholder 默认情况下,在图片加载的时候占位符图片已经被加载了,此标志表示会在图片已经加载完毕后,延迟占位符的加载
- SDWebImageTransformAnimatedImage 我们通常不对动画图片执行transformDownloadedImage的委托方法,因为大部分转码会破坏它们,此标志是用来对它们进行转码的
- SDWebImageAvoidAutoSetImage 在默认情况下,图片下载后会马上添加在UIImageView上。但是在某些情况下,我们手动设置这个图片(e:给图片应用过滤器或者添加交叉淡出的动画),使用这个标志,我们可以在图片加载成功的时候对该图片进行手工设置
progressBlock 图片加载过程中执行的闭包
completedBlock 图片加载完成执行的闭包(不管加载是否成功)
typedef void(^SDWebImageCompletionBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL);
该函数完成后的闭包,首先会执行
[self sd_cancelCurrentImageLoad];
从downloader进程中取消最新图片的加载
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
给设置图片的UIImageView设置一个关联属性imageURLKey(可能是跟之后的缓存有关系),值为:Url
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
self.image = placeholder;
});
}
如果options参数不是SDWebImageDelayPlaceholder,就执行dispatch_main_async_safe(^{
这里dispatch_main_async_safe是SDWebImage的宏定义:
self.image = placeholder;
});主线程异步队列
,然后去设置UIImage的image为占位符图
接下来这里对url进行一个判断
if (url) {
···
}
当Url等于nil
dispatch_main_async_safe(^{
[self removeActivityIndicator];
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
将一个UIActivityIndicatorView从UIImage中移除,当完成加载图片的闭包不是空的时候,生成一个错误类型,修改completedBlock的参数
当Url不等于nil
if ([self showActivityIndicatorView]) {
[self addActivityIndicator];
}
[self showActivityIndicatorView]
获取UIImage的关联属性UIActivityIndicatorView,如果没有关联过,返回false
[self addActivityIndicator];
给关联属性UIActivityIndicatorView
初始化(如果初始化了就不初始化了)并设置属性
__weak __typeof(self)wself = self;
typeof(self) 是获取到self的类型,这样定义出的wself就是和self一个类型的, 加上weak是建立一个弱引用,整句就是给self定义了一个若引用
性质的替身;
这个一般用在使用block
时会用到,因为block会copy它内部的变量,可能会造成引用循环,使用weak性质的self替代self,可以切断block对self的引用,避免循环引用
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
}];
这里调用SDWebImageManager
去下载url的图片(这个之后会详细讲下)
[wself removeActivityIndicator];
在闭包里面用UIImage的引用,去remove UIImage中的UIActivityIndicatorView
if (!wself) return;
如果wself等于nil就退出闭包了(可能是因为在闭包中,不知道之前的UIImage是否还在,所以我们需要判断下)
dispatch_main_sync_safe(^{
if (!wself) return;
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
{
completedBlock(image, error, cacheType, url);
return;
}
else if (image) {
wself.image = image;
[wself setNeedsLayout];
} else {
if ((options & SDWebImageDelayPlaceholder)) {
wself.image = placeholder;
[wself setNeedsLayout];
}
}
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
这里在主线程同步队列执行:再次对wself进行判断
如果Image不为nil、completeBolck不为nil同时options等于SDWebImageAvoidAutoSetImage 执行completedBlock(image, error, cacheType, url);
如果Image不为nil、但是completeBolck为nil或者options不等于 SDWebImageAvoidAutoSetImage,说明图片已经加载完毕了,最UIImage的image进行赋值wself.image = image;
然后执行[wself setNeedsLayout];
更新布局
当Image为nil或者completeBolck为nil同时options等于SDWebImageAvoidAutoSetImage,说明此时图片没有加载完毕,执行wself.image = placeholder;
最后,如果completeBolck不为nil并且finished为True(加载完毕?)更新completeBolck
[self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
根据UIImageViewImageLoad
取消Operation,然后获取UIView的关联属性loadOperationKey,然后再给这个属性设置一个operation操作,关键词还是UIImageViewImageLoad