iOS开发-你真的会用SDWebImage?

2016-05-17 hosea_zhou 有意思啊

原创作者:hosea_zhou

原文地址:
http://www.jianshu.com/p/dabc0c6d083e

猿吧 - 资源共享论坛:

http://www.coderbar.cn

最近论坛里有最新的视频资料哦!

SDWebImage作为目前最受欢迎的图片下载第三方框架,使用率很高。但是你真的会用吗?本文接下来将通过例子分析如何合理使用SDWebImage。

使用场景:自定义的UITableViewCell上有图片需要显示,要求网络状态为WiFi时,显示图片高清图;网络状态为蜂窝移动网络时,显示图片缩略图。如下图样例:

图中显示的图片符合根据网络状态下载要求

由于要监听网络状态,在这里笔者推荐使用AFNetWorking。

  1. 在GitHub或者利用cocoaPod给项目导入第三方框架AFNetWorking。

  2. 在AppDelegate.m文件中的application:didFinishLaunchingWithOptions:方法中监听网络状态。

// AppDelegate.m 文件中

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{

// 监控网络状态

[[AFNetworkReachabilityManager sharedManager] startMonitoring];

}

// 以下代码在需要监听网络状态的方法中使用

AFNetworkReachabilityManager *mgr = [AFNetworkReachabilityManager sharedManager];

if (mgr.isReachableViaWiFi)     { // 在使用Wifi, 下载原图

} else     { // 其他,下载小图

}

}

  • 这时就会有iOS学习者开始抱怨:这不是很简单吗?于是三下五除二写完了以下代码。

// 利用MVC,在设置cell的模型属性时候,下载图片

- setItem:(CustomItem *)item

{

_item = item;

UIImage *placeholder = [UIImage imageNamed:@"placeholderImage"];

AFNetworkReachabilityManager *mgr = [AFNetworkReachabilityManager sharedManager];

if (mgr.isReachableViaWiFi) { // 在使用Wifi, 下载原图

[self.imageView sd_setImageWithURL:[NSURL URLWithString:item.originalImage] placeholderImage:placeholder];

} else { // 其他,下载小图

[self.imageView sd_setImageWithURL:[NSURL URLWithString:item.thumbnailImage] placeholderImage:placeholder];

}

}

  • 此时,确实能完成基本的按照当前网络状态下载对应的图片,但是真实开发中,这样其实是不合理的。以下是需要注意的细节:

  1. SDWebImage会自动帮助开发者缓存图片(包括内存缓存,沙盒缓存),所以我们需要设置用户在WiFi环境下下载的高清图,下次在蜂窝网络状态下打开应用也应显示高清图,而不是去下载缩略图。

  2. 许多应用设置模块带有一个功能:移动网络环境下仍然显示高清图。这个功能其实是将设置记录在沙盒中,关于数据保存到本地,可以查看本人另一篇简书首页文章

  3. iOS本地数据存取,看这里就够了

  4. 当用户处于离线状态时候,无法合理处理业务。

  • 于是,开始加以改进。为了让读者你更容易理解,我先贴出伪代码:

- setItem:(CustomItem *)item

{

_item = item;

if (缓存中有原图)

{

self.imageView.image = 原图;

} else

{

if (Wifi环境)

{

下载显示原图

} else if (手机自带网络)

{

if (3G\4G环境下仍然下载原图)

{

下载显示原图

} else

{

下载显示小图

}

} else

{

if (缓存中有小图)

{

self.imageView.image = 小图;

} else  // 处理离线状态

{

self.imageView.image = 占位图片;

}

}

}

}

  • 实现上面的伪代码:读者可以一一对应上面的伪代码。练习的时候推荐先写伪代码,再写真实代码

  • 多多注意注释解释。

- setItem:(CustomItem *)item

{

_item = item;

// 占位图片

UIImage *placeholder = [UIImage imageNamed:@"placeholderImage"];

// 从内存\沙盒缓存中获得原图,

UIImage *originalImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:item.originalImage];

if (originalImage) { // 如果内存\沙盒缓存有原图,那么就直接显示原图(不管现在是什么网络状态)

self.imageView.image = originalImage;

} else { // 内存\沙盒缓存没有原图

AFNetworkReachabilityManager *mgr = [AFNetworkReachabilityManager sharedManager];

if (mgr.isReachableViaWiFi) { // 在使用Wifi, 下载原图

[self.imageView sd_setImageWithURL:[NSURL URLWithString:item.originalImage] placeholderImage:placeholder];

} else if (mgr.isReachableViaWWAN) { // 在使用手机自带网络

//     用户的配置项假设利用NSUserDefaults存储到了沙盒中

//    [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"alwaysDownloadOriginalImage"];

//    [[NSUserDefaults standardUserDefaults] synchronize];

#warning 从沙盒中读取用户的配置项:在3G\4G环境是否仍然下载原图

BOOL alwaysDownloadOriginalImage = [[NSUserDefaults standardUserDefaults] boolForKey:@"alwaysDownloadOriginalImage"];

if (alwaysDownloadOriginalImage) { // 下载原图

[self.imageView sd_setImageWithURL:[NSURL URLWithString:item.originalImage] placeholderImage:placeholder];

} else { // 下载小图

[self.imageView sd_setImageWithURL:[NSURL URLWithString:item.thumbnailImage] placeholderImage:placeholder];

}

} else { // 没有网络

UIImage *thumbnailImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:item.thumbnailImage];

if (thumbnailImage) { // 内存\沙盒缓存中有小图

self.imageView.image = thumbnailImage;

} else { // 处理离线状态,而且有没有缓存时的情况

self.imageView.image = placeholder;

}

}

}

}

解决了吗?真正的坑才刚刚开始。

  • 在表述上述代码的坑之前,我们先来分析一下UITableViewCell的缓存机制。

  • 请看下图:有一个tableView正在同时显示三个UITableViewCell,每个tableViewCell包含一个imageView的子控件,而且每个cell都有一个对应的模型属性用来设置imageView的图片内容。

  • 注意:由于没有cell被推出屏幕,此时缓存池为空。

cell还没有被推入缓存池

  • 当有一个cell被推到屏幕之外时,系统会自动将这个cell放入自动缓存池。注意:cell对应的UIImage图片数据模型并没有清空!还是指向上一个使用的cell。

cell被放入缓存池

  • 当下一个cell进入屏幕,系统会根据tableView注册的标识找到对应的cell,拿来应用。上述进入缓存池的cell被重新添加进tableView,在tableView的Data Source方法tableView: cellForRowAtIndexPath:中设置改cell对应的模型数据,此时cell对应的如图:

cell被放入缓存池

  • 以上就是tableView重用机制的简单介绍。

重新回来,那么上面所说的真正的坑在哪呢?

  • 用一个场景来描述一下吧:当用户所处环境WiFi网速不够快(不能立即将图片下载完毕),而在上述代码,在WiFi环境下又是下载高清大图。所以需要一定的时间来完成下载。而就在此时,用户不愿等,想看看上次打开App时显示的图片,此时用户会滑到处于下面的cell来查看。注意:此时,上面的cell下载图片操作并没有暂停,还在处于下载图片状态中。当用户在查看上次打开App的显示图片时(上次打开App下载完成的图片,SDWebImage会帮我们缓存,不用下载),而正好需要显示上次打开App时的图片的cell是利用tableView重用机制而从缓存池中拿出来的cell,等到处于上面的cell的高清大图已经下载好了的时候,SDWebImage默认做法是,立马把下载好的图片设置给ImageView,所以我们这时候会在底下的显示的cell显示上面的图片,造成数据错乱,这是非常严重的BUG。

那么该如何解决这个棘手的问题呢?

  • 如果我们能在cell被从缓存池中拿出来使用的时候,将这个cell放入缓存池之前的下载操作移除,那么就不会出现数据错乱了。

  • 这时候你可能会问我:怎么移除下载操作?下载操作不是SDWebImage帮我们做的吗?

  • 说的没错,确实是SDWebImage帮我们下载图片的,我们来扒一扒SDWebImage源码,看看他是怎么完成的。

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {

// 关闭当前图片的下载操作

[self sd_cancelCurrentImageLoad];

objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

if (!(options & SDWebImageDelayPlaceholder)) {

dispatch_main_async_safe(^{

self.image = placeholder;

});

}

if (url) {

// check if activityView is enabled or not

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)

{

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);

}

});

}];

[self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];

} else {

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);

}

});

}

}

我们惊奇的发现,原来SDWebImage在下载图片时,第一件事就是关闭imageView当前的下载操作!

  • 是不是开始感叹SDWebImage多么神奇了?没错,我们只需要把我们写的那段代码所有的直接访问本地缓存代码利用SDWebImage进行设置就OK了!

  • 下面就是完成版代码。

- setItem:(CustomItem *)item

{

_item = item;

// 占位图片

UIImage *placeholder = [UIImage imageNamed:@"placeholderImage"];

// 从内存\沙盒缓存中获得原图

UIImage *originalImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:item.originalImage];

if (originalImage) { // 如果内存\沙盒缓存有原图,那么就直接显示原图(不管现在是什么网络状态)

[self.imageView sd_setImageWithURL:[NSURL URLWithString:item.originalImage] placeholderImage:placeholder];

} else { // 内存\沙盒缓存没有原图

AFNetworkReachabilityManager *mgr = [AFNetworkReachabilityManager sharedManager];

if (mgr.isReachableViaWiFi) { // 在使用Wifi, 下载原图

[self.imageView sd_setImageWithURL:[NSURL URLWithString:item.originalImage] placeholderImage:placeholder];

} else if (mgr.isReachableViaWWAN) { // 在使用手机自带网络

//     用户的配置项假设利用NSUserDefaults存储到了沙盒中

//    [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"alwaysDownloadOriginalImage"];

//    [[NSUserDefaults standardUserDefaults] synchronize];

#warning 从沙盒中读取用户的配置项:在3G\4G环境是否仍然下载原图

BOOL alwaysDownloadOriginalImage = [[NSUserDefaults standardUserDefaults] boolForKey:@"alwaysDownloadOriginalImage"];

if (alwaysDownloadOriginalImage) { // 下载原图

[self.imageView sd_setImageWithURL:[NSURL URLWithString:item.originalImage] placeholderImage:placeholder];

} else { // 下载小图

[self.imageView sd_setImageWithURL:[NSURL URLWithString:item.thumbnailImage] placeholderImage:placeholder];

}

} else { // 没有网络

UIImage *thumbnailImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:item.thumbnailImage];

if (thumbnailImage) { // 内存\沙盒缓存中有小图

[self.imageView sd_setImageWithURL:[NSURL URLWithString:item.thumbnailImage] placeholderImage:placeholder];

} else {

[self.imageView sd_setImageWithURL:nil placeholderImage:placeholder];

}

}

}

}

本文就介绍到这里,如有疑问或错误,欢迎指出。喜欢就点个关注吧。

IOS-SDWebImage根据网络状态加载图片的更多相关文章

  1. android优化从网络中加载图片速度。。

    从网络中加载图片主要要注意两个方面的问题: 1.内存管理:图片占的内存很大,假如图片数量多,很容易让系统抛出out of memory的异常. 同时我们也要注意不同android版本中内存管理的区别. ...

  2. iOS开发之--从URL加载图片

    + (UIImage *) imageFromURLString: (NSString *) urlstring { // This call is synchronous and blocking ...

  3. iOS网络加载图片缓存策略之ASIDownloadCache缓存优化

    iOS网络加载图片缓存策略之ASIDownloadCache缓存优化   在我们实际工程中,很多情况需要从网络上加载图片,然后将图片在imageview中显示出来,但每次都要从网络上请求,会严重影响用 ...

  4. 【转】Android循环滚动广告条的完美实现,封装方便,平滑过渡,从网络加载图片,点击广告进入对应网址

    Android循环滚动广告条的完美实现,封装方便,平滑过渡,从网络加载图片,点击广告进入对应网址 关注finddreams,一起分享,一起进步: http://blog.csdn.net/finddr ...

  5. android列表停止滚动,加载图片,较为通用的一种办法

    在Adapter的itemView里面,判断列表是否在滚动中,其实是比较麻烦的,可能耦合性会比较严重. 所以考虑了下,是否能在itemView里面,检测列表的滚动状态,并监听停止状态加载图片,实现it ...

  6. iOS网络加载图片缓存与SDWebImage

    加载网络图片可以说是网络应用中必备的.如果单纯的去下载图片,而不去做多线程.缓存等技术去优化,加载图片时的效果与用户体验就会很差. 一.自己实现加载图片的方法 tips: *iOS中所有网络访问都是异 ...

  7. iOS: imageIO完成渐进加载图片

    imageIO完成渐进加载图片 不得不说,人都是有惰性的,一个月又快结束了,这个月虽说有点儿忙,但是绝对不差写几篇博客的时间,有时间去n次桌球厅,有时间玩n把英雄联盟,所谓小撸怡情大撸伤身,这个月游戏 ...

  8. ios UITableView 异步加载图片并防止错位

    UITableView 重用 UITableViewCell 并异步加载图片时会出现图片错乱的情况 对错位原因不明白的同学请参考我的另外一篇随笔:http://www.cnblogs.com/lesl ...

  9. 【iOS入门】UITableView加载图片

    学习带图片的列表 官方 LazyTableImages demo  http://download.csdn.net/detail/jlyidianyuan/5726749 分析源码是学习的好方法. ...

随机推荐

  1. 最全的Eclipse使用快捷键

    Eclipse 是一种基于 Java 的可扩展开源开发平台.尽管 Eclipse 是使用 Java 语言开发的,但它的用途并不限于 Java 语言,Eclipse 还包括插件开发环境等,下面将为大家介 ...

  2. SQL 数据库无法附加,提示 MDF" 已压缩

    SQL 数据库无法附加,提示 MDF" 已压缩,但未驻留在只读数据库或文件组中.必须将此文件解压缩 1右键点击数据库所在的文件夹,  2点击属性,在常规选项卡中点击高级,  3在弹出的窗口中 ...

  3. CNI Proposal 摘要

    原文连接:https://github.com/containernetworking/cni/blob/master/SPEC.md General consideration CNI的想法是先让容 ...

  4. mybatis分享

    Mybatis入门 一.Mybatis环境搭建及简单实例 pom.xml mybatis-config.xml <?xml version="1.0" encoding=&q ...

  5. Linux环境配置全局jdk和局部jdk并生效

    全局jdk配置: 1.root用户登录 2.进入opt目录,新建java文件夹 cd  /opt mkdir java  上传jdk7u79linuxx64.tar.gz包到java文件夹并解压 jd ...

  6. 使用pycharm操作django

    新建项目,选择已经建立好的虚拟环境 进入指令界面 新建app 添加一些文件和文件夹用于以后存放各种数据 settings设置 TEMPLATES设置 TEMPLATES = [ { 'BACKEND' ...

  7. Python(递归)

    递归函数 在函数内部,可以调用其他函数.如果一个函数在内部调用自身本身,这个函数就是递归函数. 举个例子,我们来计算阶乘n! = 1 x 2 x 3 x ... x n,用函数fact(n)表示,可以 ...

  8. 数据结构:JAVA实现二叉查找树

    数据结构:JAVA实现二叉查找树 写在前面 二叉查找树(搜索树)是一种能将链表插入的灵活性与有序数组查找的高效性结合在一起的一种数据结构. 观察二叉查找树,我们发现任何一个节点大于左子节点且小于其右子 ...

  9. class_options

    一.class_option :stylesheet, :type => :boolean, :default => "true", :description => ...

  10. PAT 天梯赛 L1-021. 重要的话说三遍 【水】

    题目链接 https://www.patest.cn/contests/gplt/L1-021 AC代码 #include <iostream> #include <cstdio&g ...