您的位置:首页 > 其它

图片加载

2015-12-26 13:32 218 查看
图片在主线程下载–>网速慢, 会出现界面卡顿

将下载放在子线程:

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];
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: