通读SDWebImage②--视图分类
对于视图分类,我们最熟悉的当属`UIImageView+WebCache`这个分类了。通常在为一个UIImageView设置一张网络图片并让SD自动缓存起来就会使用这个分类下的`- (void)sd_setImageWithURL:(NSURL *)url;`方法,如果想要设置占位图,则使用了可以传递占位图的方法。本文会从这个方法入手介绍一些视图分类的使用。首先我们要看一下SD中有关视图的几个分类:
```objectivec
UIView+WebCacheOperation // 将操作与视图绑定和取消绑定
UIImageView+WebCache // 对UIImageView设置网络图片,实现异步下载、显示、同时实现缓存
UIImageView+HighlightedWebCache // 与UIImageView+WebCache的功能完全一致,只是将image设置给UIImageView的highlightedImage属性而不是image属性
MKAnnotationView+WebCache // 与UIImageView+WebCache的功能完全一致,只是将image设置给了MKAnnotationView的image属性
UIButton+WebCache // 功能很强大,可以设置不同的state的BackgroundImage或者Image
```
下面我们先看一下所有的视图分类都依赖的UIView的分类:`UIView+WebCacheOperation `
### UIView+WebCacheOperation
为方便找到和管理视图的正在进行的一些操作,SD将每一个视图的实例和它正在进行的操作(下载和缓存的组合操作)绑定起来,实现操作和视图的一一对应关系,以便可以随时拿到视图正在进行的操作,控制其取消等。
具体的实现是使用runtime给UIView绑定了一个属性,这个属性的key是static char loadOperationKey
的地址,
这个属性是NSMutableDictionary类型,value为操作,key是针对不同类型的视图和不同类型的操作设定的字符串
为什么要使用static char loadOperationKey
的地址作为属性的key,实际上很多第三方框架在给类绑定属性的时候都会使用这种方案(如AFN),这样做有以下几个好处:
1.占用空间小,只有一个字节。
2.静态变量,地址不会改变,使用地址作为key总是唯一的且不变的。
3.避免和其他框架定义的key重复,或者其他key将其覆盖的情况。比如在其他文件(仍然是UIView的分类)中定义了同名同值的key,使用objc_setAssociatedObject进行设置绑定的属性的时候,可能会将在别的文件中设置的属性值覆盖。
UIView+WebCacheOperation
这个分类提供了三个方法,用于操作绑定关系。
// 返回绑定的属性
- (void)sd_setImageLoadOperation:(id)operation forKey:(NSString *)key;
// 对绑定的字典属性setObject
- (void)sd_cancelImageLoadOperationWithKey:(NSString *)key;
// 对绑定的字典属性removeObject
- (void)sd_removeImageLoadOperationWithKey:(NSString *)key;
需要注意对绑定值setObject的时候的一些细节:
- (void)sd_setImageLoadOperation:(id)operation forKey:(NSString *)key {
// 若这个key对应的操作本来就有且正在执行,那么先将这个操作取消,并将它移除。
[self sd_cancelImageLoadOperationWithKey:key];
// 然后设置新的操作
NSMutableDictionary *operationDictionary = [self operationDictionary];
[operationDictionary setObject:operation forKey:key];
}
### UIImageView+WebCache、UIImageView+HighlightedWebCache、MKAnnotationView+WebCache
这一部虽然标题设置了三个分类,但是我们主要讲解UIImageView+WebCache,在本文的开始就说到,另外两个分类的实现是完全一致的,而且代码重复度为99%(丝毫没有夸张)。
UIImageView+WebCache,最熟悉的就是以下几个为UIImage设置图片网络的方法:
```objectivec
- sd_setImageWithURL:
- sd_setImageWithURL: placeholderImage:
- sd_setImageWithURL: placeholderImage: options:
sd_setImageWithURL: completed:
sd_setImageWithURL: placeholderImage: completed:
sd_setImageWithURL: placeholderImage: options: completed:
sd_setImageWithURL: placeholderImage: options: progress: completed:
sd_setImageWithPreviousCachedImageWithURL: placeholderImage: options: progress: completed:
但无论是使用哪个方法,它们的实现上都是调用了`- sd_setImageWithURL: placeholderImage: options: progress: completed:`方法,只是传递的参数不同。(插语:带方法描述的语言就是麻烦,省略参数做起来都复杂)。
下面我们看一下`- sd_setImageWithURL: placeholderImage: options: progress: completed:`方法的实现:
```objectivec
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_cancelCurrentImageLoad]; // 移除UIImageView当前绑定的操作。这一句非常关键,当在TableView的cell包含了的UIImageView被重用时,首先调用这一行代码,保证这个ImageView的下载和缓存组合操作都被取消。如果①上次赋值的图片正在下载,则下载不再进行;②下载完成了,但还没有执行到调用回调(回调包含wself.image = image) ,由于操作被取消,因而不会显示和重用的cell相同的图片;③以上两种情况只有在网速极慢和手机处理速度极慢的情况下才会发生,实际上发生的概率非常小,大多数是这种情况:操作已经进行到下载完成了,这次使用的cell是一个重用的cell,而且保留着imageView的image,对于这种情况SD会用下面的设置占位图的语句,将image暂时设置为占位图,如果占位图为空,就意味着先暂时清空image。
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC); // 将传入的url与self绑定
// 如果没有设置延迟加载占位图,设置image为占位图
// 这句代码要结合上面的理解,实际上在这个地方SD埋了一个bug,如果设置了SDWebImageDelayPlaceholder选项,会忽略占位图,而如果imageView在重用的cell中,这时会显示重用着的image。
// 我建议将下面的两句改为
/*
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
self.image = placeholder;
});
} else {
dispatch_main_async_safe(^{
self.image = nil;
});
}
*/
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
self.image = placeholder;
});
}
if (url) {
// 检查是否通过`setShowActivityIndicatorView:`方法设置了显示正在加载指示器。如果设置了,使用`addActivityIndicator`方法向self添加指示器
if ([self showActivityIndicatorView]) {
[self addActivityIndicator];
}
__weak __typeof(self) wself = self;
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
[wself removeActivityIndicator]; // 移除加载指示器
if (!wself) return;
dispatch_main_sync_safe(^{
if (!wself) return;
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
{ // 如果设置了禁止自动设置image选项,则不会执行`wself.image = image;`,而是直接执行完成回调,有用户自己决定如何处理。
completedBlock(image, error, cacheType, url);
return;
}
else if (image) {
// 设置image
wself.image = image;
[wself setNeedsLayout];
} else { // image为空,并且设置了延迟设置占位图,会将占位图设置为最终的image
if ((options & SDWebImageDelayPlaceholder)) {
wself.image = placeholder;
[wself setNeedsLayout];
}
}
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
}];
// 为UIImageView绑定新的操作
[self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
} else { // 判断url不存在,移除加载指示器,执行完成回调,传递错误信息。
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);
}
});
}
}
这个就是完整的加载网络图片的过程,而具体的如何实现下载细节、网络访问验证、在下载完成之后如何进行内存和磁盘缓存的,请参照上一篇文章的内容。
上面的所有的为UIImageView设置网络图片的方法中有一个和其他稍微不同的- sd_setImageWithPreviousCachedImageWithURL: placeholderImage: options: progress: completed:
,其实也就是张的有点不同,它的实现是这样的:
- (void)sd_setImageWithPreviousCachedImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:url];
UIImage *lastPreviousCachedImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:key];
[self sd_setImageWithURL:url placeholderImage:lastPreviousCachedImage ?: placeholder options:options progress:progressBlock completed:completedBlock];
}
可以看到,它的思路是先取得上次缓存的图片,然后作为占位图的参数再次进行一次图片设置。
在设置图片的过程中,有关如何移除和添加加载指示器的两个方法,我们这里不做讨论,其实是对系统的UIActivityIndicatorView
视图的使用。
还有一个需要的方法- (void)sd_setAnimationImagesWithURLs:(NSArray *)arrayOfURLs
,要注意的是这个方法传递的参数是一个由URL组成的数组,这个方法用来设置UIImage的animationImages属性。它的实现思路是:
将遍历URL数组中的元素,根据每个URL创建一个下载操作并执行,在回调里面对imationImages属性值追加下载好的image。它的具体实现如下:
- (void)sd_setAnimationImagesWithURLs:(NSArray *)arrayOfURLs {
[self sd_cancelCurrentAnimationImagesLoad];
__weak __typeof(self)wself = self;
NSMutableArray *operationsArray = [[NSMutableArray alloc] init];
for (NSURL *logoImageURL in arrayOfURLs) {
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:logoImageURL options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (!wself) return;
dispatch_main_sync_safe(^{
__strong UIImageView *sself = wself;
[sself stopAnimating]; // 先动画停止
if (sself && image) {
NSMutableArray *currentImages = [[sself animationImages] mutableCopy];
if (!currentImages) {
currentImages = [[NSMutableArray alloc] init];
}
[currentImages addObject:image]; // 追加新下载的image
sself.animationImages = currentImages;
[sself setNeedsLayout];
}
[sself startAnimating];
});
}];
[operationsArray addObject:operation];
}
// 注意这里绑定的不是单个操作,而是操作数据。UIView+WebCacheOperation的方法`sd_cancelImageLoadOperationWithKey:`也对操作数组做了适配
[self sd_setImageLoadOperation:[NSArray arrayWithArray:operationsArray] forKey:@"UIImageViewAnimationImages"];
}
### UIButton+WebCache
有关`UIButton+WebCache`分类中的方功能确实强大:可以为image的不同state(Normal、Highlighted、Disabled、Selected)设置不同的backgoud图片或者image图片,但是它的实现很简单,几乎和上面介绍的UIImageView的设置方法是相同的,只是UIButton多了一个管理不同state下的url的功能。
UIButton管理图片的url其实也是通过runtime绑定属性来实现的,和UIImageView不同的是:UIImageView只需一张图片所以就绑定了NSURL类型值,而UIButton需要多张图片且要区分state,所以使用NSMutableDictionary来存储图片的URL,其中key是@(state),value是该state对应的图片的url。需要注意的是它只是存储了image的URL,而并没有存储backgroudImage的URL。
- (NSMutableDictionary *)imageURLStorage {
NSMutableDictionary *storage = objc_getAssociatedObject(self, &imageURLStorageKey);
if (!storage)
{
storage = [NSMutableDictionary dictionary];
objc_setAssociatedObject(self, &imageURLStorageKey, storage, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return storage;
}
在- sd_setImageWithURL: forState: placeholderImage: options: completed:
对它的调用:
[self.imageURLStorage removeObjectForKey:@(state)];
self.imageURLStorage[@(state)] = url;
通读SDWebImage②--视图分类的更多相关文章
- 通读SDWebImage①--总体梳理、下载和缓存
本文目录 下载操作SDWebImageDownloaderOptions和下载过程实现 下载管理SDWebImageDownloader 缓存SDImageCache SDWebImageManage ...
- 通读SDWebImage③--gif和webP的支持、不同格式图片的处理、方向处理
本文目录 NSData+ImageContentType: 根据NSData获取MIME UIImage+GIF UIImage+WebP UIImage+MultiFormat:根据NSData相应 ...
- Oracle视图分类及各种操作讲解(超级好文)
目录:一.视图的定义: 二.视图的作用: 三.创建视图: 1.权限 2.语法 3.1 创建简单视图 3.2 创建连接视图 3.2.1 连接视图定义 3.2.2 创建连接视图 3.2.3 ...
- UI基础:UI程序执行顺序(UIApplicationMain()函数),自定义视图 分类: iOS学习-UI 2015-07-02 22:09 68人阅读 评论(0) 收藏
UI程序的一般执行顺序: 先进入main里面,执行函数UIApplicationMain(),通过该函数创建应用程序对象和指定其代理并实现监听,当执行函数UIApplicationMain()时还会做 ...
- ASP.NET Core 5.0 MVC中的视图分类——布局视图、启动视图、具体视图、分部视图
一.创建MVC应用程序 创建后的项目 二.(全局性)启动视图 _ViewStart.cshtml 顾名思义,就是在View开始执行之前执行,而且是每一个View, 它的预设内容是 @{ Layout ...
- SDWebImage之SDWebImageManager
SDWebImageManager是SDWebImage的核心类.它拥有一个SDWebImageCache和一个SDWebImageDownloader属性,分别用于图片的缓存和下载处理.虽然是核心类 ...
- SDWebImage源码分析
1.概述 SDWebImage是iOS开发中,被广泛使用的一个第三方开源库,提供了图片从加载.解析.处理.缓存.清理等一些列功能,让我们能够专心于业务的处理.本篇会从SDWebImage的源码,来一步 ...
- iOS系列 基础篇 05 视图鼻祖 - UIView
iOS系列 基础篇 05 视图鼻祖 - UIView 目录: UIView“家族” 应用界面的构建层次 视图分类 最后 在Cocoa和Cocoa Touch框架中,“根”类时NSObject类.同样, ...
- Oracle学习总结_day06_视图&序列&索引
本文为博主辛苦总结,希望自己以后返回来看的时候理解更深刻,也希望可以起到帮助初学者的作用. 转载请注明 出自 : luogg的博客园 谢谢配合! day 06 视图,索引,序列 视图 什么是视图: 视 ...
随机推荐
- iOS Swift的一些小知识(不断补充)
1. 在swift文件里是不能写c语言函数的,不兼容c,直接报错.想调用c语言函数,就要利用系统提供的桥接功能,就如同swfit中调用oc一样! 2.swift 2.0后提供了@convention( ...
- Jquery操作select
<select id="Select1"> <option value="one">一</option> <optio ...
- 【python】nuitka封装python
官网:http://nuitka.net/doc/user-manual.html python打包工具对比:http://blog.csdn.net/qwemicheal/article/detai ...
- EXCEL 对比数据是否重复
1.同一列 后一行对比前面所有行 查找是否重复 =IF(COUNTIF(B$2:B2,B2)>1,"重复","") 2.两行两列(多行多列) 两行两列 = ...
- PHP AES的加密解密
AES加密算法 密码学中的高级加密标准(Advanced Encryption Standard,AES),又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准.这个标准用来替代原先的DE ...
- Hadoop中的问题排查思路
一.概述: 在实际使用hadoop的过程中,由于涉及到多台服务器.每台机器上可能还有多个服务等.所以当集群环境出现问题时,快速定位到错误出现的地方尤为重要. 在排查错误的过程中,基本上就是通过既有的工 ...
- /usr/bin/ld.bfd.real: cannot find -lGL /usr/bin/ld.bfd.real: cannot find -lX11
/usr/bin/ld.bfd.real: cannot find -lGL /usr/bin/ld.bfd.real: cannot find -lX11 根据网上大多数的说法,以及官网的介绍.截至 ...
- [UWP]创建自定义VisualState Trigger
这篇博客将介绍在UWP程序中如何创建和使用自定义VisualState Trigger. 上一篇博客中介绍了如何使用AdaptiveTrigger.目前UWP内置的StateTrigger只有Adap ...
- ORACLE中常见SET指令
1 SET TIMING ON 说明:显示SQL语句的运行时间.默认值为OFF. 在SQLPLUS中使用,时间精确到0.01秒.也就是10毫秒. 在PL/SQL DEVELOPER 中 ...
- WebADNuke整理
在webconfig上增添 <webadnuke> <database> <add name="SqlDbProvider" type="C ...