前言
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
LearnTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier forIndexPath:indexPath];
// [cell.img sd_setImageWithURL:[NSURL URLWithString:_dataList[indexPath.row]]];
NSURL *url = [NSURL URLWithString:_dataList[indexPath.row]];
NSData *imageData = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:imageData];
cell.img.image = image;
return cell;
}
初级加载网络图片方式, 具有以下缺点和解决方法:
- UI不流畅 -> 子线程下载图片
- 图片重复下载 -> 利用图片本地缓存
2.1 使用字典进行内存缓存
2.2 使用沙盒进行磁盘缓存
使用字典进行内存缓存
/** 内存缓存 */
@property (nonatomic, strong) NSMutableDictionary *images;
UIImage *image = [self.images objectForKey:appM.icon];
if (image) {
cell.imageView.image = image;
NSLog(@"%zd处的图片使用了内存缓存中的图片",indexPath.row) ;
} else {
//把图片保存到内存缓存
[self.images setObject:image forKey:<#UrlStr#>];
}
使用沙盒进行磁盘缓存
//保存图片到沙盒缓存
NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
//获得图片的名称,不能包含/
NSString *fileName = [<#UrlStr#> lastPathComponent];
//拼接图片的全路径
NSString *fullPath = [caches stringByAppendingPathComponent:fileName];
//检查磁盘缓存
NSData *imageData = [NSData dataWithContentsOfFile:fullPath];
if (imageData) {
UIImage *image = [UIImage imageWithData:imageData];
cell.imageView.image = image;
NSLog(@"%zd处的图片使用了磁盘缓存中的图片",indexPath.row) ;
//把图片保存到内存缓存
[self.images setObject:image forKey:appM.icon];
} else {
....省略网络下载....
//写数据到沙盒
[imageData writeToFile:fullPath atomically:YES];
}
子线程下载图片
/** 队列 */
@property (nonatomic, strong) NSOperationQueue *queue;
-(NSOperationQueue *)queue
{
if (_queue == nil) {
_queue = [[NSOperationQueue alloc]init];
//设置最大并发数
_queue.maxConcurrentOperationCount = 5;
}
return _queue;
}
NSBlockOperation *download = [NSBlockOperation blockOperationWithBlock:^{
NSURL *url = [NSURL URLWithString:_dataList[indexPath.row]];
NSData *imageData = [NSData dataWithContentsOfURL:url];
//写数据到沙盒
[imageData writeToFile:fullPath atomically:YES];
UIImage *image = [UIImage imageWithData:imageData];
[NSThread sleepForTimeInterval:2.0];
// 线程间通讯, 主线程刷新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
cell.img.image = image;
NSLog(@"%zd处网络下载图片",indexPath.row);
}];
}];
[self.queue addOperation:download];
多线程讨论:
- 这里的用到睡眠2秒钟来模拟网速差的情况, 如果不使用多线程, 程序会阻塞在主线程, 出现明显卡顿的现象.
- 上面代码还有一个隐蔽的缺陷, 例如当第一个cell 开始下载图片, 在两秒钟的时间内反复拖动cell 程序仍然会判断需要走下载图片流程. 继续优化....
线程操作做内存缓存
/** 操作缓存 */
@property (nonatomic, strong) NSMutableDictionary *operations;
-(NSMutableDictionary *)operations
{
if (_operations == nil) {
_operations = [NSMutableDictionary dictionary];
}
return _operations;
}
三步操作:
- 判断是否存在线程操作
- 添加线程操作
- 完成后移除操作
//检查该图片时候正在下载,如果是那么久什么都捕捉,否则再添加下载任务
NSBlockOperation *download = [self.operations objectForKey:_dataList[indexPath.row]];
if (download) {
} else {
download = [NSBlockOperation blockOperationWithBlock:^{
.....
//移除图片的下载操作
[self.operations removeObjectForKey:_dataList[indexPath.row]];
......
}];
//添加操作到操作缓存中
[self.operations setObject:download forKey:_dataList[indexPath.row]];
......
}
还有两个bug问题:
- 由于cell重用, 图片错乱问题 -> 如果发现正在下载就清除图片或者加载占位图片
//检查该图片时候正在下载,如果是那么久什么都捕捉,否则再添加下载任务
NSBlockOperation *download = [self.operations objectForKey:lover.icon];
cell.img.image = [UIImage imageNamed:@"知道错了"];
if (download) {
}.....
- 执行闪退的问题
图片添加内存缓存 图片为空值, 这个错误出现在网络加载图片后
[self.images setObject:image forKey:lover.icon];
解决方法就是判断为空 就及时 移除下载图片线程操作并return
download = [NSBlockOperation blockOperationWithBlock:^{
NSURL *url = [NSURL URLWithString:lover.icon];
NSData *imageData = [NSData dataWithContentsOfURL:url];
//写数据到沙盒
[imageData writeToFile:fullPath atomically:YES];
UIImage *image = [UIImage imageWithData:imageData];
if (image == nil) {
//移除图片的下载操作
[self.operations removeObjectForKey:lover.icon];
return ;
}......
讨论:
以上是多图下载缓存的一个深入分析, 难点在于线程通讯, 还有一些细节处理
参考:
小马哥的多线程教程