多图下载综合案例
- tableView- cell- cell上面有图标、标题、子标题
- 图片来源:网络下载图片app.plist
- 耗时操作,需要在子线程中执行
搭建界面
- storyboard:tableViewController
- is initial
- class:ViewController
- 描述cell
- style:subtitle
- indentifier
- ViewController:UITableViewCotroller
数据展示
-
提供数据源
- 定义数组属性
- 懒加载
- 加载plist文件
- 字典数组转成模型数组
- 创建可变数组
- 遍历字典数组
- 把每一个字典转成模型
-
创建模型
- 模型属性name/icon/download
- 提供一个类方法:appWithDict:
-
实现数据源方法
- 多少组
- 每组有多少行
- 定义cell视图
- 创建cell
- 设置cell数据:标题,子标题,图片
- 图片下载
- URL
- 二进制数据下载到本地
- 转换格式
- 图片下载
- 返回cell
修改配置文件:ATS特性
缓存处理
-
问题1:UI卡顿
- 原因:把下载图片的耗时操作都放在主线程执行
- 解决:开启子线程来下载图片
-
问题2:拖动图片的时候,图片重复下载
- 耗流量
- 原因:滚动的时候会重新调用cellForRowAtIndextPath来显示图片
- 解决:把下载的图片保存起来,下载图片之前,先检查本地有没有,如果有直接设置图片,就不重复下载了
使用字典:一一对应
-
定义一个可变字典属性
- 懒加载,判断有没有做初始化处理,做初始化处理
思路:当把图片下载图片完成之后,需要把该图片保存到内存缓存当中;在需要显示图片的时候,先检查本地的缓存中是否已经下载了该图片;如果缓存中有该图片,直接设置就可以了,如果缓存中没有该图片,此时需要去下载图片
-
把图片保存到内存缓存
- setObject:image forKey:name/dowload/icon
- 建议用url作为key值
- setObject:image forKey:name/dowload/icon
尝试去字典里去取值,用key值去取
-
[self.images objectForKey:]
- 如果有,设置图片
- 如果没有,下载图片
-
问题:保存到内存里了,是用属性接收图片的,退出程序后,重新运行程序,图片还是会重新下载
- 怎么做到永远只下载一次
- 如何改善缓存结构,改成二级缓存结构(沙盒缓存)
二级缓存结构
【内存缓存 沙盒缓存】
-
磁盘缓存(沙盒缓存)
- 保存到内存缓存中的字典是变量,程序退出后就不存在了
- 当图片下载完成之后,除了保存到内存缓存中之外,还需要保存一份到磁盘缓存中
- 当图片需要显示的时候,先检查内存缓存,如果内存缓存中有数据,那么就直接设置数据
- 如果内存缓存中没有数据,那么再去检查磁盘缓存
- 怎么检查磁盘缓存?
- 看路径对应的文件是否存在
- 检查磁盘缓存dataWithContentOfFile:
- 怎么检查磁盘缓存?
- 如果磁盘缓存中有数据,就直接设置就可以了 ,保存一份到内存缓存中
- 显示图片
- 保存一份到内存缓存中setObject:forKey
- 如果没有数据,再去下载数据
-
保存到磁盘的哪里?
- Documents:备份,不允许存缓存
- Library
- caches:缓存文件一般存到这里
- preference:偏好设置
- tmp:临时路径,随时可能被删除
-
步骤
- 获得缓存路径NSSearchPathForDirectorieesInDomains()
- NSCachesDirectory
- NSUerDomainMask
- YES
- lastObject
- 文件名称
- NSURL拿到路径的最后一个节点
- lastPathComponent
- 拼接全路径
- 文件路径+文件名称
- stringByAppendingPathComponent:
- 写文件
- 图片是不能直接写文件的,数组和字典可以写文件
- 拿到图片的二进制数据
- data writeToFile:atomically:
- 获得缓存路径NSSearchPathForDirectorieesInDomains()
开子线程下载图片
- 创建队列NSOperationQueue
- 封装操作
- NSBlockOperation blockOperationWithBlock:
- 添加操作到队列
- UI刷新操作放到主线程
- NSOperation mainQueue]addOperationWithBlock
- 注意:在cellForRow中方法中创建了很多Queue,所以,定义一个属性,懒加载中创建队列
- 问:为什么图片不显示?只有cell滚动的时候才能显示?
- 最开始的时候图片的尺寸为0,imageView的大小是根据图片执行的
- 操作是异步执行的,当cell要显示的时候调用cellForRow设置图片,开子线程下载是异步执行的,可以先跳过代码块,回过头来再执行
- 解决:重新刷新,不能刷新整个tableView
- 刷新一行reloadRowsAtIndexPaths:withRowanimation:
完善
模拟下载该图片需要花费较长的时间,模拟网络不好的情况
-
bug:图片又重复下载
- 分析:如果cell的第0个图片下载需要10s,在下载到第三秒的时候,拖动cell,第0行cell放到了缓存池中,当继续拖动cell,第0个cell又要显示的时候,又开始重新下载图片
- 先检查图片的下载操作是否已经存在
- 存在:等待
- 不存在:封装图片下载操作并且添加到队列
- 怎么判断不存在?尝试去取download
NSBlockOperation * download = [self.operations objectForKey:appM.icon]
- 怎么判断不存在?尝试去取download
-
bug:当数据错乱的时候,程序容易崩溃
- 数据后台传给我们的,当数据不对的时候,程序就会出现问题,会崩掉
- 原因:把图片保存到内存缓存的时候,image是空[self.images setObject:image forKey:appM.icon];image为nil,字典里面是不能保存空值的
- 解决:在设置之前判断image有没有值
-
bug:刷新tableView,数据错乱,显示图片和标题不对应
- cell复用的问题
- 两种方法解决数据错乱问题
- 直接清空cell.imageView.image = nil,这样做法会给用户一种错觉,会认为cell本身没有图片
- 设置一个占位图片,先搞一个本地的图片cell.imageView.image = [UIImage imageName:]
bug:图片没有下载图片没有下载成功,下载要显示的时候,要尝试重新下载,还需要把它从缓存中移除if(image ==nil){
//从缓存池中移除
[self.operations removeObjectForKey]
return;
}还需要细节的bug需要处理
SDWebImage实现
专门处理图片下载和图片缓存的
为Cocoa Touch框架提供一个UIImageView分类,加载图片进行缓存处理
异步图片下载
异步存储+具备自动缓存过期的磁盘映像缓存
支持GIF播放
支持WebP格式
背景图像压缩
保证同一个url图片资源不被多次下载
保证错误url图片资源不被多次下载
保证不会阻塞主线程
高性能
使用GCD和ARC
支持arm64架构
面试:框架内部实现细节
中文文档SDWebImage-About框架的注释
-
SDWebImage
- 改造多图下载项目
- 一行代码
- cell.imageView sd_setImageWithURL:placeholderImage:设置图片
- 内部做了内存缓存和磁盘缓存
- 图片尺寸的问题:
- 框架本身的问题
- 自定义cell里面设置图片的frame
- layoutSubviews
- 开发中cell一般都需要自定义,就可以在xib上设置frame了