iOS图片多线程下载和缓存

前言

- (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;
}

初级加载网络图片方式, 具有以下缺点和解决方法:

  1. UI不流畅 -> 子线程下载图片
  2. 图片重复下载 -> 利用图片本地缓存
    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];

多线程讨论:

  1. 这里的用到睡眠2秒钟来模拟网速差的情况, 如果不使用多线程, 程序会阻塞在主线程, 出现明显卡顿的现象.
  2. 上面代码还有一个隐蔽的缺陷, 例如当第一个cell 开始下载图片, 在两秒钟的时间内反复拖动cell 程序仍然会判断需要走下载图片流程. 继续优化....

线程操作做内存缓存

/** 操作缓存 */
@property (nonatomic, strong) NSMutableDictionary *operations;
-(NSMutableDictionary *)operations
{
    if (_operations == nil) {
        _operations = [NSMutableDictionary dictionary];
    }
    return _operations;
}

三步操作:

  1. 判断是否存在线程操作
  2. 添加线程操作
  3. 完成后移除操作
//检查该图片时候正在下载,如果是那么久什么都捕捉,否则再添加下载任务
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问题:

  1. 由于cell重用, 图片错乱问题 -> 如果发现正在下载就清除图片或者加载占位图片
//检查该图片时候正在下载,如果是那么久什么都捕捉,否则再添加下载任务
NSBlockOperation *download = [self.operations objectForKey:lover.icon];
cell.img.image = [UIImage imageNamed:@"知道错了"];
if (download) {
    
}.....
  1. 执行闪退的问题
    图片添加内存缓存 图片为空值, 这个错误出现在网络加载图片后
[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 ;
                    }......

讨论:

以上是多图下载缓存的一个深入分析, 难点在于线程通讯, 还有一些细节处理

参考:
小马哥的多线程教程

你可能感兴趣的:(iOS图片多线程下载和缓存)