图片在主线程下载–>网速慢, 会出现界面卡顿
将下载放在子线程:
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];
问题: 图片不显示
分析:
解决办法:
// 0. 占位图像
UIImage *placeholder = [UIImage imageNamed:@"user_default"];
cell.imageView.image = placeholder;
问题: 每次和 cell 交互,layoutSubviews 方法会根据图像的大小自动调整 imageView 的尺寸
解决办法: 自定义cell
问题: 这样图片大小变化问题解决了, 但是由于网络图片加载速度不一致, 用户滚动列表, 可能会出现图片错行
解决办法: MVC–将网络下载的图片保存在模型中, 这样设置图片的时候从cell对应的模型中取, 就不会错行了.
#import
/// 下载的图像
@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];
}];
问题: 如果图像下载很慢,用户滚动表格很快,会造成重复创建下载操作
解决办法: 使用”下载操作缓存池”
什么是缓存池? 我们目前接触到得缓存池有:
UITableViewCell 就是使用NSSet做缓存, 在缓存池中随便娶一个cell复用
如果上拉或下拉, 索引就不对啦, 无法标示下载操作
可以使用图像的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, 收到内存警告需要释放图片, 而图片与模型绑定, 不方便释放, 怎么办??
解决办法: 使用图像缓存, 收到内存警告, 直接释放图片缓存池
/// 图像缓存池
@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];
}