1数据准备, 建立一个appInfo模型 ,在视图控制器中懒加载数组,实现字典转模型,加载模型数组
NSMutableArray *data = [NSMutableArray array];
NSMutableArray *list = [NSMutableArray arrayWithCapacity:data.count];
arrayWithCapacity: 容量,指定数组容量,在实例化数组的同时,准备好容量指定的空间,
假如是10,一次性在内存中准备好10个空间,再添加元素的时候就不会再次申请内存,如果增加第十一个元素,会再次开辟十个内存空间,
[NSMutableArray array] 每次添加一个元素临时申请内存空间
显然上面的性能比下面的要高很多
将可变数组变成不可变的—>有助于线程安全,外部不能修改
使用代码块遍历数组要比for快
2 同步加载图片的效果 在主线程中直接加载
// 同步加载图像
// 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];
cell.imageView.image = image;
存在问题:
如果网速慢,会卡爆了!影响用户体验
滚动表格,会重复下载图像,造成用户经济上的损失,大量耗费流量
破法 —>异步加载图片
3异步下载图像
全局操作队列
// 全局队列,统一管理所有下载操作@property (nonatomic, strong) NSOperationQueue *downloadQueue;
懒加载
- (NSOperationQueue *)downloadQueue { if (_downloadQueue == nil) { _downloadQueue = [[NSOperationQueue alloc] init]; } return _downloadQueue; }
异步下载
// 异步加载图像
// 1. 定义下载操作
// 异步加载图像
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交互,会调用layoutsubviews,重新调整布局
破法 : 0 使用占位图像
1 使用自定义cell
cell.nameLabel.text = app.name;
cell.downloadLabel.text = app.download;
// 异步加载图像
// 0. 占位图像
UIImage *placeholder = [UIImage imageNamed:@"user_default"];
cell.iconView.image = placeholder;
// 1. 定义下载操作
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.iconView.image = image;
}];
}];
// 2. 将下载操作添加到队列
[self.downloadQueue addOperation:downloadOp];
问题: 如果网络图片下载速度,不一致,同时用户滚动图片,可能显示图片错行问题
修改延时代码,查看错误
// 1. 模拟延时
if (indexPath.row > 9) {
[NSThread sleepForTimeInterval:3.0];
}
上下滚动一下表格即可看到 cell 复用的错误
破法 : MVC
image
属性#import <UIKit/UIKit.h>/// 下载的图像@property (nonatomic, strong) UIImage *image;
判断模型中是否已经存在图像
if (app.image != nil) { NSLog(@"加载模型图像..."); cell.iconView.image = app.image; return cell; }
下载完成后设置模型图像
// 3. 主线程更新 UI[[NSOperationQueue mainQueue] addOperationWithBlock:^{ // 设置模型中的图像 app.image = image; // 刷新表格 [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; }];
问题
如果图像下载很慢,用户滚动表格很快,会造成重复创建下载操作
修改延时代码
// 1. 模拟延时if (indexPath.row == 0) { [NSThread sleepForTimeInterval:10.0]; }
快速滚动表格,将第一行不断“滚出/滚入”界面可以查看操作被重复创建的问题
操作缓冲池
所谓缓冲池,其实就是一个容器,能够存放多个对象
数组:按照下标,可以通过 indexPath
可以判断操作是否已经在进行中
无法解决上拉&下拉刷新
NSSet -> 无序的
无法定位到缓存的操作
字典
:按照key
,可以通过下载图像的 URL
(唯一定位网络资源的字符串)
小结:选择字典作为操作缓冲池
/// 操作缓冲池
@property (nonatomic, strong) NSMutableDictionary *operationCache;
懒加载
- (NSMutableDictionary *)operationCache { if (_operationCache == nil) {
_operationCache = [NSMutableDictionary dictionary];
} return _operationCache;
}
判断下载操作是否被缓存——正在下载
// 异步加载图像// 0. 占位图像UIImage *placeholder = [UIImage imageNamed:@"user_default"];
cell.iconView.image = placeholder;// 判断操作是否存在if (self.operationCache[app.icon] != nil) { NSLog(@"正在玩命下载中..."); return cell;
}
将操作添加到操作缓冲池
// 2. 将操作添加到操作缓冲池[self.operationCache setObject:downloadOp forKey:app.icon];// 3. 将下载操作添加到队列[self.downloadQueue addOperation:downloadOp];
修改占位图像的代码位置,观察会出现的问题
下载完成后,将操作从缓冲池中删除
[self.operationCache removeObjectForKey:app.icon];
弱引用 self
的编写方法:
__weak typeof(self) weakSelf = self;
利用 dealloc
辅助分析
- (void)dealloc { NSLog(@"我去了");
}
注意
如果使用 self
,视图控制器会在下载完成后被销毁
而使用 weakSelf
,视图控制器在第一时间被销毁
不用重复下载,利用MVC刷新表格,不会造成数据混乱
所有下载后的图像,都会记录在模型中
如果模型数据本身很多(2000),单纯图像就会占用很大的内存空间
如果图像和模型绑定的很紧,不容易清理内存
使用图像缓存池
缓存属性
/// 图像缓冲池@property (nonatomic, strong) NSMutableDictionary *imageCache;
懒加载
- (NSMutableDictionary *)imageCache { if (_imageCache == nil) {
_imageCache = [[NSMutableDictionary alloc] init];
} return _imageCache;
}
删除模型中的 image
属性
哪里出错改哪里!
image == nil 时会崩溃=>不能向字典中插入 nil
image == nil 时会重复刷新表格,陷入死循环
修改主线程回调代码
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
if (image != nil) {
// 设置模型中的图像
[weakSelf.imageCache setObject:image forKey:app.icon];
// 刷新表格
[weakSelf.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
}
}]
- (void)downloadImage:(NSIndexPath *)indexPath { // 1. 根据 indexPath 获取数据模型
AppInfo *app = self.appList[indexPath.row]; // 2. 判断操作是否存在
if (self.operationCache[app.icon] != nil) { NSLog(@"正在玩命下载中..."); return;
} // 3. 定义下载操作
__weak typeof(self) weakSelf = self;
NSBlockOperation *downloadOp = [NSBlockOperation blockOperationWithBlock:^{ // 1. 模拟延时
NSLog(@"正在下载 %@", app.name); if (indexPath.row == 0) {
[NSThread sleepForTimeInterval:3.0];
} // 2. 异步加载网络图片
NSURL *url = [NSURL URLWithString:app.icon]; NSData *data = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:data]; // 3. 主线程更新 UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{ // 将下载操作从缓冲池中删除
[weakSelf.operationCache removeObjectForKey:app.icon]; if (image != nil) { // 设置模型中的图像
[weakSelf.imageCache setObject:image forKey:app.icon]; // 刷新表格
[weakSelf.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
}
}];
}]; // 4. 将操作添加到操作缓冲池
[self.operationCache setObject:downloadOp forKey:app.icon]; // 5. 将下载操作添加到队列
[self.downloadQueue addOperation:downloadOp];
}
如果接收到内存警告,程序一定要做处理,工作中的程序一定要处理,否则后果很严重!!!
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// 1. 取消下载操作
[self.downloadQueue cancelAllOperations];
// 2. 清空缓冲池
[self.operationCache removeAllObjects];
[self.imageCache removeAllObjects];
}
黑名单
如果网络正常,但是图像下载失败后,为了避免再次都从网络上下载该图像,可以使用“黑名单”
黑名单属性
@property (nonatomic, strong) NSMutableArray *blackList;
懒加载
- (NSMutableArray *)blackList { if (_blackList == nil) {
_blackList = [NSMutableArray array];
} return _blackList;
}
下载失败记录在黑名单中
if (image != nil) {
// 设置模型中的图像
[weakSelf.imageCache setObject:image forKey:app.icon];
// 刷新表格
[weakSelf.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
} else {
// 下载失败记录在黑名单中
[weakSelf.blackList addObject:app.icon];
}
判断黑名单
// 2.1 判断黑名单
if ([self.blackList containsObject:app.icon]) {
NSLog(@"已经将 %@ 加入黑名单...", app.icon);
return;
}