SDWebImage源码解读之分类
第十一篇
前言
我们知道SDWebImageManager
是用来管理图片下载的,但我们平时的开发更多的是使用UIImageView
和UIButton
这两个控件显示图片。
按照正常的想法,我们只需要在他们的分类中,通过SDWebImageManager
把图片下载下载之后,再进行赋值就行了。但这样的设计并不是最好的设计,我们在准备提供一项功能的时候,应该要尽可能的弄明白这个功能的使用者是谁?这些使用者的共同点是什么?
UIImageView
和UIButton
这两个控件都继承自UIView
。那么我们是不是可以给UIView
赋予加载图片的功能,然后,
UIImageView
和UIButton
不就都有这个能力了吗?
除了讲解这些之外,在本文的最后,我们给view的layer也添加这个能力。
UIView+WebCache
/**
* Set the imageView `image` with an `url` and optionally a placeholder image.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
* @param operationKey A string to be used as the operation key. If nil, will use the class name
* @param setImageBlock Block used for custom set image code
* @param progressBlock A block called while image is downloading
* @note the progress block is executed on a background queue
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
实现方法:
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
/// 每一个view都给他绑定一个operation,通过一个key来获取这个operation
/// 如果operationKey为nil,就采用他自身的类的字符串作为key
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
/// 这是一个view的分类方法,目的是取消之前绑定的operation
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
/// 为自身绑定一个URL
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
/// 如果不是延迟显示placeholder的情况
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
/// 在图片下载下来之前,先给自身赋值一个placeholder
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
}
if (url) {
// check if activityView is enabled or not
/// 如果设置了显示加载动画,就添加ActivityIndicatorView
if ([self sd_showActivityIndicatorView]) {
[self sd_addActivityIndicator];
}
/// 开始下载图片
__weak __typeof(self)wself = self;
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
__strong __typeof (wself) sself = wself;
/// 下载完成后关闭动画
[sself sd_removeActivityIndicator];
if (!sself) {
return;
}
dispatch_main_async_safe(^{
if (!sself) {
return;
}
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
completedBlock(image, error, cacheType, url);
return;
} else if (image) {
[sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
[sself sd_setNeedsLayout];
} else {
/// 如果是设置了SDWebImageDelayPlaceholder,那么就会在下载完成之后给自身赋值placeholder
if ((options & SDWebImageDelayPlaceholder)) {
[sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
[sself sd_setNeedsLayout];
}
}
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
}];
/// 绑定operation
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} else {
dispatch_main_async_safe(^{
[self sd_removeActivityIndicator];
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
}
}
其实这个核心的实现方法也很简单,逻辑也不复杂,需要注意一下下边的方法:
- (void)sd_setImage:(UIImage *)image imageData:(NSData *)imageData basedOnClassOrViaCustomSetImageBlock:(SDSetImageBlock)setImageBlock {
if (setImageBlock) {
setImageBlock(image, imageData);
return;
}
#if SD_UIKIT || SD_MAC
if ([self isKindOfClass:[UIImageView class]]) {
UIImageView *imageView = (UIImageView *)self;
imageView.image = image;
}
#endif
#if SD_UIKIT
if ([self isKindOfClass:[UIButton class]]) {
UIButton *button = (UIButton *)self;
[button setImage:image forState:UIControlStateNormal];
}
#endif
}
这个方法说明如果我们设置了setImageBlock
,那么view的显示图片的逻辑应该写到这个Block中,如果setImageBlock
为nil,就判断是不是UIImageView
和UIButton
。
UIImageView的实现
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
[self sd_internalSetImageWithURL:url
placeholderImage:placeholder
options:options
operationKey:nil
setImageBlock:nil
progress:progressBlock
completed:completedBlock];
}
CALay的实现
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
NSString *validOperationKey = @"CALayerImages" ?: NSStringFromClass([self class]);
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
self.contents = (__bridge id _Nullable)placeholder.CGImage;
});
}
if (url) {
__weak __typeof(self)wself = self;
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
__strong __typeof (wself) sself = wself;
if (!sself) {
return;
}
dispatch_main_async_safe(^{
if (!sself) {
return;
}
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
completedBlock(image, error, cacheType, url);
return;
} else if (image) {
self.contents = (__bridge id _Nullable)image.CGImage;
} else {
if ((options & SDWebImageDelayPlaceholder)) {
self.contents = (__bridge id _Nullable)placeholder.CGImage;
}
}
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
}];
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} else {
dispatch_main_async_safe(^{
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
}
}
- (SDOperationsDictionary *)operationDictionary {
SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
if (operations) {
return operations;
}
operations = [NSMutableDictionary dictionary];
objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return operations;
}
- (void)sd_setImageLoadOperation:(nullable id)operation forKey:(nullable NSString *)key {
if (key) {
[self sd_cancelImageLoadOperationWithKey:key];
if (operation) {
SDOperationsDictionary *operationDictionary = [self operationDictionary];
operationDictionary[key] = operation;
}
}
}
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
// Cancel in progress downloader from queue
SDOperationsDictionary *operationDictionary = [self operationDictionary];
id operations = operationDictionary[key];
if (operations) {
if ([operations isKindOfClass:[NSArray class]]) {
for (id <SDWebImageOperation> operation in operations) {
if (operation) {
[operation cancel];
}
}
} else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
[(id<SDWebImageOperation>) operations cancel];
}
[operationDictionary removeObjectForKey:key];
}
}
- (void)sd_removeImageLoadOperationWithKey:(nullable NSString *)key {
if (key) {
SDOperationsDictionary *operationDictionary = [self operationDictionary];
[operationDictionary removeObjectForKey:key];
}
}
实现思路跟给view添加该能力完全相同。
[self.imageView.layer sd_setImageWithURL:url placeholderImage:[UIImage imageNamed:@"4-3397163ecdb3855a0a4139c34a695885.jpg"] options:0 progress:nil completed:nil];
总结
SDWebImage
的源码就看到这里了,下一篇我会把这十一篇中用到的知识点进行一个总结。
由于个人知识有限,如有错误之处,还望各路大侠给予指出啊
- SDWebImage源码解读 之 NSData+ImageContentType 简书 博客园
- SDWebImage源码解读 之 UIImage+GIF 简书 博客园
- SDWebImage源码解读 之 SDWebImageCompat 简书 博客园
- SDWebImage源码解读 之SDWebImageDecoder 简书 博客园
- SDWebImage源码解读 之SDWebImageCache(上) 简书 博客园
- SDWebImage源码解读之SDWebImageCache(下) 简书 博客园
- SDWebImage源码解读之SDWebImageDownloaderOperation 简书 博客园
- SDWebImage源码解读之SDWebImageDownloader 简书 博客园
- SDWebImage源码解读之SDWebImageManager 简书 博客园
- SDWebImage源码解读之SDWebImagePrefetcher 简书 博客园
SDWebImage源码解读之分类的更多相关文章
- SDWebImage源码解读之干货大总结
这是我认为的一些重要的知识点进行的总结. 1.图片编码简介 大家都知道,数据在网络中是以二进制流的形式传播的,那么我们该如何把那些1和0解析成我们需要的数据格式呢? 说的简单一点就是,当文件都使用二进 ...
- SDWebImage源码解读 之 NSData+ImageContentType
第一篇 前言 从今天开始,我将开启一段源码解读的旅途了.在这里先暂时不透露具体解读的源码到底是哪些?因为也可能随着解读的进行会更改计划.但能够肯定的是,这一系列之中肯定会有Swift版本的代码. 说说 ...
- SDWebImage源码解读 之 UIImage+GIF
第二篇 前言 本篇是和GIF相关的一个UIImage的分类.主要提供了三个方法: + (UIImage *)sd_animatedGIFNamed:(NSString *)name ----- 根据名 ...
- SDWebImage源码解读之SDWebImageManager
第九篇 前言 SDWebImageManager是SDWebImage中最核心的类了,但是源代码确是非常简单的.之所以能做到这一点,都归功于功能的良好分类. 有了SDWebImageManager这个 ...
- SDWebImage源码解读之SDWebImageDownloaderOperation
第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...
- SDWebImage源码解读 之 SDWebImageCompat
第三篇 前言 本篇主要解读SDWebImage的配置文件.正如compat的定义,该配置文件主要是兼容Apple的其他设备.也许我们真实的开发平台只有一个,但考虑各个平台的兼容性,对于框架有着很重要的 ...
- SDWebImage源码解读_之SDWebImageDecoder
第四篇 前言 首先,我们要弄明白一个问题? 为什么要对UIImage进行解码呢?难道不能直接使用吗? 其实不解码也是可以使用的,假如说我们通过imageNamed:来加载image,系统默认会在主线程 ...
- SDWebImage源码解读之SDWebImageCache(上)
第五篇 前言 本篇主要讲解图片缓存类的知识,虽然只涉及了图片方面的缓存的设计,但思想同样适用于别的方面的设计.在架构上来说,缓存算是存储设计的一部分.我们把各种不同的存储内容按照功能进行切割后,图片缓 ...
- SDWebImage源码解读之SDWebImageCache(下)
第六篇 前言 我们在SDWebImageCache(上)中了解了这个缓存类大概的功能是什么?那么接下来就要看看这些功能是如何实现的? 再次强调,不管是图片的缓存还是其他各种不同形式的缓存,在原理上都极 ...
随机推荐
- JDBC 数据库连接池
http://www.cnblogs.com/lihuiyy/archive/2012/02/14/2351768.html JDBC 数据库连接池 小结 当对数据库的访问不是很频繁时,可以在每次 ...
- MemSQL 取代 HDFS 与 Spark 结合,性能大幅提升
MemSQL 取代 HDFS 与 Spark 结合,性能大幅提升 3,597 次阅读 - 基础架构 Apache Spark是目前非常强大的分布式计算框架.其简单易懂的计算框架使得我们很容易理解.虽然 ...
- 外网主机访问虚拟机下的Web服务器_服务器应用_Linux公社-Linux系统门户网站
body{ font-family: "Microsoft YaHei UI","Microsoft YaHei",SimSun,"Segoe UI& ...
- html5绘图
html5绘图 这是我在绘图过程中遇到的问题,求助高手帮忙啊... 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ...
- [转]ASP.NET Core 1 Deploy to IIS
本文转自: http://webmodelling.com/webbits/aspnet/aspnet-deploy-iis.aspx 15 Sep 2016. This tutorial will ...
- WINDOWS动态链接库--MFC规则动态链接库
第一代window程序员使用windows api进行编程,到了后来,微软推出MFC类库,于是,动态链接库进行了升级,可以在动态连接库中使用MFC的API,这就叫做MFC动态链接库, 其中MFC动态链 ...
- csv和excel的区别
excel 文件只能通过excel打开,里面包含公式或者计算. csv文件是一种通用数据格式,可以用很多方式打开,比如excel.csv 以分割数据,用行分割符号分割行级数据,直接上个例子一目了然. ...
- for/foreach/linq效率测试
; Random r = new Random(); List<byte> list = new List<byte>(); Console.WriteLine("开 ...
- 对AD域进行定期自动备份设置图解
今天为大家讲解一下,如何对域进行定期的备份,因为如果域出问题了,在公司里那可就不好玩了啊,对做定期备份,在域出问题的时候可以及时恢复,减少对域重建而浪费大量的时间,同样也耽误公司员工的工作,这样的事情 ...
- iOS开发——导入第三方库引起的unknown type name 'NSString'
今天加入SVProgressHUD的第三方库的时候报了24个错误( too many errors emitted, stopping now),都是 expected identifier or ' ...