图片加载
2015-12-26 13:32
218 查看
图片在主线程下载–>网速慢, 会出现界面卡顿
将下载放在子线程:
问题: 图片不显示
分析:
使用的是系统提供的 cell
异步方法中只设置了图像,但是没有设置 frame
图像加载后,一旦与 cell 交互,会调用 cell 的 layoutSubviews
方法,重新调整 cell 的布局
解决办法:
使用占位图片
问题: 每次和 cell 交互,layoutSubviews 方法会根据图像的大小自动调整 imageView 的尺寸
解决办法: 自定义cell
问题: 这样图片大小变化问题解决了, 但是由于网络图片加载速度不一致, 用户滚动列表, 可能会出现图片错行
解决办法: MVC–将网络下载的图片保存在模型中, 这样设置图片的时候从cell对应的模型中取, 就不会错行了.
在模型中添加Image属性
2 . 使用MVC更新表格图像
问题: 如果图像下载很慢,用户滚动表格很快,会造成重复创建下载操作
解决办法: 使用”下载操作缓存池”
什么是缓存池? 我们目前接触到得缓存池有:
NSSet: 特点无序
UITableViewCell 就是使用NSSet做缓存, 在缓存池中随便娶一个cell复用
NSArray: 特点有序, 通过下标Index获取
如果上拉或下拉, 索引就不对啦, 无法标示下载操作
NSDictionary: 键值对存储
可以使用图像的URL来做key(URL是唯一的)
分析:
实现:
1> 增加一个字典属性作为操作缓存池
2> 懒加载
3> 判断下载操作是否被缓存——正在下载
4> 将操作添加到操作缓存池
问题:
我们把操作都添加到缓存池中, 没有进行释放, 随着图片数量增加, 会有内存隐患.
解决办法: 下载结束后, 从缓存池清除下载操作
问题: 小心block中的self会形成循环引用
循环引用分析:
[self.operationCache removeObjectForKey:app.icon]; 下载操作结束后便移除, 由此打破了循环引用;
当然此处没有循环引用, 在这里介绍一个解除循环引用的办法:
应用:
分析: 在这里使用weakSelf的好处–
当indexPath.row == 0时, 模拟了10″延时;
使用self, 控制器已经pop出去, 但是控制器释放是在10″延时+图片下载+设置图片之后才被销毁;
如果使用weakSelf, 当控制器被pop出去, 控制器会立马销毁, 不会等待图片的操作;
由此可见, 使用weakSelf更好!
问题: 我们目前采用从模型中获取图片, 但是当图片数量过多如20000, 收到内存警告需要释放图片, 而图片与模型绑定, 不方便释放, 怎么办??
解决办法: 使用图像缓存, 收到内存警告, 直接释放图片缓存池
增加一个图片缓存池属性 NSMutableDictionary
懒加载
判断图像缓存池中是否存在图像
将图片保存到图片缓存池
最后一个问题: 出现内存警告, 清除缓存
将下载放在子线程:
NSBlockOperation *downloadOp = [NSBlockOperation blockOperationWithBlock:^{ // 1. 模拟延时 NSLog(@"正在下载 %@", app.name); [NSThread sleepForTimeInterval:0.5]; // 2. 异步加载网络图片 NSURL *url = [NSURL URLWithString:app.icon]; NSData *data = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:data]; // 3. 主线程更新 UI [[NSOperationQueue mainQueue] addOperationWithBlock:^{ cell.imageView.image = image; }]; }]; // 2. 将下载操作添加到队列 [self.downloadQueue addOperation:downloadOp];
问题: 图片不显示
分析:
使用的是系统提供的 cell
异步方法中只设置了图像,但是没有设置 frame
图像加载后,一旦与 cell 交互,会调用 cell 的 layoutSubviews
方法,重新调整 cell 的布局
解决办法:
使用占位图片
// 0. 占位图像 UIImage *placeholder = [UIImage imageNamed:@"user_default"]; cell.imageView.image = placeholder;
问题: 每次和 cell 交互,layoutSubviews 方法会根据图像的大小自动调整 imageView 的尺寸
解决办法: 自定义cell
问题: 这样图片大小变化问题解决了, 但是由于网络图片加载速度不一致, 用户滚动列表, 可能会出现图片错行
解决办法: MVC–将网络下载的图片保存在模型中, 这样设置图片的时候从cell对应的模型中取, 就不会错行了.
在模型中添加Image属性
#import <UIKit/UIKit.h> /// 下载的图像 @property (nonatomic, strong) UIImage *image;
2 . 使用MVC更新表格图像
// 判断模型中是否存在图像 if (app.image != nil) { NSLog(@"加载模型图像..."); cell.iconView.image = app.image; return cell; }
// 主线程更新 UI [[NSOperationQueue mainQueue] addOperationWithBlock:^{ // 设置模型中的图像 app.image = image; // 刷新表格 [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; }];
问题: 如果图像下载很慢,用户滚动表格很快,会造成重复创建下载操作
解决办法: 使用”下载操作缓存池”
什么是缓存池? 我们目前接触到得缓存池有:
NSSet: 特点无序
UITableViewCell 就是使用NSSet做缓存, 在缓存池中随便娶一个cell复用
NSArray: 特点有序, 通过下标Index获取
如果上拉或下拉, 索引就不对啦, 无法标示下载操作
NSDictionary: 键值对存储
可以使用图像的URL来做key(URL是唯一的)
分析:
实现:
1> 增加一个字典属性作为操作缓存池
/// 操作缓冲池 @property (nonatomic, strong) NSMutableDictionary *operationCache;
2> 懒加载
- (NSMutableDictionary *)operationCache { if (_operationCache == nil) { _operationCache = [NSMutableDictionary dictionary]; } return _operationCache; }
3> 判断下载操作是否被缓存——正在下载
// 判断操作是否存在 if (self.operationCache[app.icon] != nil) { NSLog(@"正在玩命下载中..."); return cell; }
4> 将操作添加到操作缓存池
// 2. 将操作添加到操作缓存池 [self.operationCache setObject:downloadOp forKey:app.icon]; // 3. 将下载操作添加到队列 [self.downloadQueue addOperation:downloadOp];
问题:
我们把操作都添加到缓存池中, 没有进行释放, 随着图片数量增加, 会有内存隐患.
解决办法: 下载结束后, 从缓存池清除下载操作
[[NSOperationQueue mainQueue] addOperationWithBlock:^{ // 下载结束,从缓冲池中清除下载操作 // 提示:NSMutableDictionary 不是线程安全的,因此需要在主线程中删除!可以保证安全! // 如果在Image加载出来删除操作, 外部环境是异步执行, 可能两个线程同时删除一个操作, 会导致程序崩溃 [self.operationCache removeObjectForKey:app.icon]; // 将图像保存在模型 app.image = image; // 刷新当前行 [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; }];
问题: 小心block中的self会形成循环引用
循环引用分析:
[self.operationCache removeObjectForKey:app.icon]; 下载操作结束后便移除, 由此打破了循环引用;
当然此处没有循环引用, 在这里介绍一个解除循环引用的办法:
// 定义 weakSelf,OC中默认都是 strong __weak typeof(self) weakSelf = self;
应用:
__weak typeof(self) weakSelf = self; NSBlockOperation *downloadOp = [NSBlockOperation blockOperationWithBlock:^{ // 模拟延时 NSLog(@"正在玩命加载中...%@", app.name); if (indexPath.row == 0) { [NSThread sleepForTimeInterval:10.0]; } NSURL *url = [NSURL URLWithString:app.icon]; NSData *data = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:data]; [[NSOperationQueue mainQueue] addOperationWithBlock:^{ [weakSelf.operationCache removeObjectForKey:app.icon]; // 将图像保存在模型 app.image = image; // 刷新当前行 [weakSelf.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; }]; }];
分析: 在这里使用weakSelf的好处–
当indexPath.row == 0时, 模拟了10″延时;
使用self, 控制器已经pop出去, 但是控制器释放是在10″延时+图片下载+设置图片之后才被销毁;
如果使用weakSelf, 当控制器被pop出去, 控制器会立马销毁, 不会等待图片的操作;
由此可见, 使用weakSelf更好!
问题: 我们目前采用从模型中获取图片, 但是当图片数量过多如20000, 收到内存警告需要释放图片, 而图片与模型绑定, 不方便释放, 怎么办??
解决办法: 使用图像缓存, 收到内存警告, 直接释放图片缓存池
增加一个图片缓存池属性 NSMutableDictionary
/// 图像缓存池 @property (nonatomic, strong) NSMutableDictionary *imageCache;
懒加载
- (NSMutableDictionary *)imageCache { if (_imageCache == nil) { _imageCache = [[NSMutableDictionary alloc] init]; } return _imageCache; }
判断图像缓存池中是否存在图像
// 判断图像缓存池中是否已经存在图像 if (self.imageCache[app.icon] != nil) { NSLog(@"从内存加载图像 %@", app.name); cell.iconView.image = self.imageCache[app.icon]; return cell; }
将图片保存到图片缓存池
// 将图像保存在缓存池 [weakSelf.imageCache setObject:image forKey:app.icon];
最后一个问题: 出现内存警告, 清除缓存
- (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // 1. 取消下载操作 [self.downloadQueue cancelAllOperations]; // 2. 清空缓冲池 [self.operationCache removeAllObjects]; [self.imageCache removeAllObjects]; }
相关文章推荐
- Mbps MB/S Mb/s
- LeetCode Search a 2D Matrix
- 2015 中国大数据技术大会 PPT 尝鲜
- Linux命令学习总结:dos2unix - unix2dos
- 初识socket
- zw.delphi不同版本程序运行速度测试
- 麦克风阵列原理
- 二叉树的建立与遍历
- hdu1072 Nightmare--DFS/BFS
- 期末总结
- python 程序构架
- 判断101-200之间有多少个素数,并输出所有素数。
- 文本挖掘详解
- 查找算法
- ScrollView的使用
- 用Python脚本做一些网页游戏中力所能及的自动化任务
- 马哥linux学习笔记:centos7中的yum详解
- 防尘和防水等级
- C语言学习笔记----伊能C语言学习笔记----变量可以在程序的三个地方声明
- LWIP 移植文件内容