多图下载(SD缓存逻辑)

有这样的需求:

image.png
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
    <dict>
        <key>name</key>
        <string>植物大战僵尸</string>
        <key>icon</key>
        <string>http://p16.qhimg.com/dr/48_48_/t0125e8d438ae9d2fbb.png</string>
        <key>download</key>
        <string>10311万</string>
    </dict>
    <dict>
        <key>name</key>
        <string>捕鱼达人2</string>
        <key>icon</key>
        <string>http://p19.qhimg.com/dr/48_48_/t0101e2931181bb540d.png</string>
        <key>download</key>
        <string>9982万</string>
    </dict>
    <dict>
        <key>name</key>
        <string>保卫萝卜</string>
        <key>icon</key>
        <string>http://p17.qhimg.com/dr/48_48_/t012d281e8ec8e27c06.png</string>
        <key>download</key>
        <string>8582万</string>
    </dict>
    <dict>
        <key>name</key>
        <string>找你妹</string>
        <key>icon</key>
        <string>http://p18.qhimg.com/dr/48_48_/t0184f949337481f071.png</string>
        <key>download</key>
        <string>5910万</string>
    </dict>
    <dict>
        <key>name</key>
        <string>水果忍者</string>
        <key>icon</key>
        <string>http://p17.qhimg.com/dr/48_48_/t015f10076f95e27e74.png</string>
        <key>download</key>
        <string>5082万</string>
    </dict>
    <dict>
        <key>name</key>
        <string>鳄鱼小顽皮</string>
        <key>icon</key>
        <string>http://p16.qhimg.com/dr/48_48_/t01885f5596e1d30172.png</string>
        <key>download</key>
        <string>3918万</string>
    </dict>
    <dict>
        <key>name</key>
        <string>神偷奶爸</string>
        <key>icon</key>
        <string>http://p19.qhimg.com/dr/48_48_/t0164ad383c622aabef.png</string>
        <key>download</key>
        <string>3681万</string>
    </dict>
    <dict>
        <key>name</key>
        <string>时空猎人</string>
        <key>icon</key>
        <string>http://p17.qhimg.com/dr/48_48_/t017bc3cfcf3981b197.png</string>
        <key>download</key>
        <string>3645万</string>
    </dict>
    <dict>
        <key>name</key>
        <string>愤怒的小鸟</string>
        <key>icon</key>
        <string>http://p18.qhimg.com/dr/48_48_/t012fea7312194537c2.png</string>
        <key>download</key>
        <string>3552万</string>
    </dict>
    <dict>
        <key>name</key>
        <string>滑雪大冒险</string>
        <key>icon</key>
        <string>http://p18.qhimg.com/dr/48_48_/t01e61cbba53fb9eb82.png</string>
        <key>download</key>
        <string>3487万</string>
    </dict>
    <dict>
        <key>name</key>
        <string>爸爸去哪儿</string>
        <key>icon</key>
        <string>http://p18.qhimg.com/dr/48_48_/t0108c33d3321352682.png</string>
        <key>download</key>
        <string>3117万</string>
    </dict>
    <dict>
        <key>name</key>
        <string>我叫MT </string>
        <key>icon</key>
        <string>http://p17.qhimg.com/dr/48_48_/t01077fd80ffb5c8740.png</string>
        <key>download</key>
        <string>2386万</string>
    </dict>
    <dict>
        <key>name</key>
        <string>3D终极狂飙</string>
        <key>icon</key>
        <string>http://p17.qhimg.com/dr/48_48_/t01f55acd4a3ed024eb.png</string>
        <key>download</key>
        <string>2166万</string>
    </dict>
    <dict>
        <key>name</key>
        <string>杀手2</string>
        <key>icon</key>
        <string>http://p16.qhimg.com/dr/48_48_/t018f89d6e0922f75a1.png</string>
        <key>download</key>
        <string>1951万</string>
    </dict>
    <dict>
        <key>name</key>
        <string>俄罗斯方块</string>
        <key>icon</key>
        <string>http://p0.qhimg.com/dr/48_48_/t0183a670f1dbff380f.png</string>
        <key>download</key>
        <string>1290万</string>
    </dict>
    <dict>
        <key>name</key>
        <string>刀塔传奇</string>
        <key>icon</key>
        <string>http://p16.qhimg.com/dr/48_48_/t01c3f62a27c3de7af5.png</string>
        <key>download</key>
        <string>1249万</string>
    </dict>
</array>
</plist>

apps.plist文件中存储的是对应的model数据,图片是通过url存储,那么想要显示cell的图片,就需要先下载图片

初始代码:在tableView里加载cell中图片的代码是这样的:

image.png
  • 上面代码存在的问题:
    • 将加载图片的代码放在了主线程里面,容易造成UI卡顿,不流畅,下载图片的代码应该放在子线程
    • 还有就是,每次用户拖动tableView,一个cell重复的显示消失,会造成tableView:cellForRow这个方法的重复调用,使得同一张图片重复下载,这里是应该避免的,所以应该做缓存

改进一:做内存缓存,解决重复下载问题
重复下载的原因:每次用户拖动tableview,一个cell反复出现在视野,那么cell的数据源方法会重复调用,造成重复下载

  • 通过上面分析,对代码做下面处理:

    • 设置一张图片,首先检查有没有缓存,有就直接设置图片,如果没有缓存,那就下载
    • 定义一个可变字典属性作为内存缓存:@property(nonatomic,strong)NSMutableDictionary *images;
    • 存图片的时候,将图片作为value,图片地址url作为key,存入字典:[self.images setObject:image forKey:appM.icon];
    • 取图片的时候就通过url去取.判断取出来的image是否为nil来判断是否已经做了这张图片的缓存.
  • 那么设置cell图片的时候就有下面的步骤:

    • 通过cell对应图片的url去内存缓存中去取图片
    • 如果没有取到图片(image==nil),就去下载图片,然后设置图片
    • 如果取到了图片(image!=nil),那么就直接设置图片

改进二:做磁盘(沙盒)缓存,解决每次启动app之后,app又会重新下载图片的问题
出现重复下载的原因:每次重新启动app内存缓存就不在了,就会重新下载图片

  • 沙盒:

    • Library
      • caches:缓存
      • preferences:偏好设置,存放一些小的数据
    • Documents:会备份的数据,官方不允许把缓存保存到这里
    • Temporary:临时路径,里面的数据随时可能被删除
  • 现在设置图片变为下面的步骤:

    • 首先检查内存缓存,因为从内存缓存中去取比较快:
      • 有内存缓存,就直接设置图片
      • 没有内存缓存,就去检查沙盒/磁盘缓存
    • 检查沙盒/磁盘缓存:
      • 通过NSData *data = NSData dataWithContentsOfFile:fullPath去取
        • 因为url里面的”/“在文件系统里面是有特殊意义的,不能直接用作图片的名称;这里做磁盘缓存有个技巧就是,用图片的url的最后一个组成部分(lastPathComponent)作为图片的名称,然后通过caches文件夹路径和图片名称拼接成图片的全路径.
      • 有磁盘缓存(data!=nil),就直接设置cell图片,然后还需要保存内存缓存,以保证后面从内存缓存中更快的取出图片;
      • 没有磁盘缓存(data==nil),就去从网络下载图片
    • 从网络下载图片:
      • 下载完毕,设置图片
      • 将图片存到内存缓存中
      • 将图片存到磁盘缓存中
  • 改进后代码如下:

//检查缓存
UIImage *image = [self.images objectForKey:appM.icon];
if(image)
{
        //直接设置
        cell.imageView.image = image;
        NSLog(@"%zd使用了内存缓存",indexPath.row);
}
else
{
        NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
        //得到图片的名称
        NSString *filename = [appM.icon lastPathComponent];
        //拼接文件的全路径
        NSString *fullPath = [cachesPath stringByAppendingPathComponent:filename];
        
        //去检查磁盘缓存
        NSData *data = [NSData dataWithContentsOfFile:fullPath];
        if(data)
        {
            UIImage *image = [UIImage imageWithData:data];
            cell.imageView.image = image;
            
            //把图片存到内存缓存中
            [self.images setObject:image forKey:appM.icon];
            
            NSLog(@"%zd使用了磁盘缓存",indexPath.row);
        }
else
        {
            NSURL *url = [NSURL URLWithString:appM.icon];
            NSData *data = [NSData dataWithContentsOfURL:url];
            UIImage *image = [UIImage imageWithData:data];
            cell.imageView.image = image;
            
            //把图片存到内存缓存中
            [self.images setObject:image forKey:appM.icon];
            
           NSLog(@"%zd直接下载",indexPath.row);
            
            //把图片保存到磁盘缓存
            [data writeToFile:fullPath atomically:YES];
        }
}

改进三:开子线程对图片进行下载,解决UI卡顿,不流畅的问题
UI卡顿的原因:主线程一般不放下载任务,不然有时候下载大的图片,会造成主线程阻塞.

  • 开一个子队列:NSOperationQueue *queue,为了不重复创建队列,将这个队列设置为属性,做一个lazy加载,同时设置最大并发数为5(最多允许5个操作);
  • 将图片的下载放在self.queue = [NSBlockOperationWithBlock:^{…}]中;
  • 设置图片的时候返回主队列设置.
  • 这里需要注意的是,虽然下载完毕回到了主线程,但因为是异步下载,return cell的时候还没把图片下载下来,等到下载完毕设置图片后,imageView就不会显示图片,此时需要刷新这一行的cell.
    • -(void)reloadRowsAtIndexPaths:(NSArray*)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
//检查缓存
    UIImage *image = [self.images objectForKey:appM.icon];
    if(image)
    {
        //直接设置
        cell.imageView.image = image;
        NSLog(@"%zd使用了内存缓存",indexPath.row);
    }else
    {
        NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
        //得到图片的名称
        NSString *filename = [appM.icon lastPathComponent];
        //拼接文件的全路径
        NSString *fullPath = [cachesPath stringByAppendingPathComponent:filename];
        
        //去检查磁盘缓存
        NSData *data = [NSData dataWithContentsOfFile:fullPath];
        //data = nil;
        if(data)
        {
            UIImage *image = [UIImage imageWithData:data];
            cell.imageView.image = image;
            
            //把图片存到内存缓存中
            [self.images setObject:image forKey:appM.icon];
            
            NSLog(@"%zd使用了磁盘缓存",indexPath.row);
        }else
        {
            //清空图片或者是设置占位图片
            cell.imageView.image = [UIImage imageNamed:@"Snip20200808_172"];
            //检查操作缓存
            NSBlockOperation *downloadOperation = [self.operations objectForKey:appM.icon];
            
            downloadOperation = [NSBlockOperation blockOperationWithBlock:^{
            NSURL *url = [NSURL URLWithString:appM.icon];
            NSData *data = [NSData dataWithContentsOfURL:url];
            
             for (NSInteger i = 0; i<1000000000; i++) 
        {
            //模拟网络忙的情况
             }
                    
             UIImage *image = [UIImage imageWithData:data];
                    
             //把图片存到内存缓存中
             [self.images setObject:image forKey:appM.icon];
                    
              NSLog(@"%zd直接下载---%@",indexPath.row,[NSThread currentThread]);
                    
              //把图片保存到磁盘缓存
              [data writeToFile:fullPath atomically:YES];
                    
              //设置图片
               [[NSOperationQueue mainQueue]addOperationWithBlock:^{
//cell.imageView.image = image;
//[tableView reloadData];
                        
               //刷新指定的行
               [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationMiddle];
           }
        }
    }

改进四:设置一个NSOperations数组对象作为操作缓存,下载图片前,判断是否已经存在当前图片的下载任务,解决网络不好会重复下载图片的情况
出现重复下载的原因:网络不好时,用户反复快速拖动TableView,一个cell里面的图片如果还没下载下来,却反复出现消失在用户的视野,那么就会造成重复下载(因为在这里还没有执行后面的将图片存到内存缓存和磁盘缓存中,再次加载cell回去检查内存缓存和磁盘缓存,但是都还没有存,因此又会下载图片)

改进五:设置占位图片,解决cell复用时,数据错乱的问题()
出现数据错乱的原因:

  • 综上,总体的思路是:
    • 首先检查内存缓存,因为从内存缓存中去取比较快:
      • 有内存缓存,就直接设置图片
      • 没有内存缓存,就去检查沙盒/磁盘缓存
    • 检查沙盒/磁盘缓存:
      • 通过NSData *data = NSData dataWithContentsOfFile:fullPath去取
        • 因为url里面的”/“在文件系统里面是有特殊意义的,不能直接用作图片的名称;这里做磁盘缓存有个技巧就是,用图片的url的最后一个组成部分(lastPathComponent)作为图片的名称,然后通过caches文件夹路径和图片名称拼接成图片的全路径.
      • 有磁盘缓存(data!=nil),就直接设置cell图片,然后还需要保存内存缓存,以保证后面从内存缓存中更快的取出图片;
      • 没有磁盘缓存(data==nil),就去从网络下载图片
    • 检查操作缓存:(操作缓存的key也是图片的url,其实这里我觉得只需要用一个数组就可以,将图片的url添加进去)
      • 已经存在下载操作,什么不做
      • 不存在则开始从网络下载图片
    • 从网络下载图片:
      • 设置占位图片
      • 下载完毕,设置图片
      • 将图片存到内存缓存中
      • 将图片存到磁盘缓存中

封装的cell:


//
//  GSYTableViewCell.m
//  多图下载
//
//  Created by gaosiyang on 16/6/17.
//  Copyright © 2016年 gaosiyang. All rights reserved.
//

#import "GSYTableViewCell.h"
@interface GSYTableViewCell()
@property (weak, nonatomic) IBOutlet UIImageView *imageV;
@property (weak, nonatomic) IBOutlet UILabel *nameL;
@property (weak, nonatomic) IBOutlet UILabel *downloadL;
//@property (nonatomic,strong) NSMutableDictionary *images;
@property (nonatomic,strong) NSURLSession *session;
//@property (nonatomic,strong) NSMutableDictionary *dataTasks;
@end
@implementation GSYTableViewCell
static NSMutableDictionary *images = nil;
static NSMutableArray *dataTasks = nil;
-(NSMutableDictionary *)images
{
    if (!images) {
        images = [NSMutableDictionary dictionary];
    }
    return images;
}

-(NSURLSession *)session
{
    if (!_session) {
        _session = [NSURLSession sharedSession];
    }
    return _session;
}

-(NSMutableArray *)dataTasks
{
    if (!dataTasks) {
        dataTasks = [NSMutableArray array];
    }
    return dataTasks;
}

-(void)setItem:(AppItem *)item
{
    _item=item;
    //设置图片
    //检查内存缓存
    [self images];
    [self dataTasks];
    UIImage *image = [images objectForKey:item.icon];
    if (image) {
        self.imageV.image = image;
        NSLog(@"内存缓存");
    }
    else
    {
        NSString *path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)lastObject];
        NSString *imageName = [item.icon lastPathComponent];
        NSString *fullPath = [path stringByAppendingPathComponent:imageName];
        NSData *data = [NSData dataWithContentsOfFile:fullPath];
        //检查磁盘缓存
        if (data) {
            self.imageV.image = [UIImage imageWithData:data];
            NSLog(@"磁盘缓存");
        }
        else
        {
            if ([dataTasks containsObject:item.icon]) {
                
            }
            else
            {
                NSLog(@"网络下载");
                //从网络下载图片
                NSURL *url = [NSURL URLWithString:item.icon];
                NSURLSession *session = [NSURLSession sharedSession];
                NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
                    UIImage *image = [UIImage imageWithData:data];
                    dispatch_async(dispatch_get_main_queue(), ^{
                        self.imageV.image = image;
                    });
                    //做内存缓存
                    [images setObject:image forKey:item.icon];
                    //做磁盘缓存
                    [data writeToFile:fullPath atomically:YES];
                }];
                [dataTask resume];
                
                //做下载任务缓存
                [dataTasks addObject:item.icon];
            }
        }
    }

    self.nameL.text = item.name;
    self.downloadL.text = item.download;
}

+(instancetype)tableViewCell
{
    return [[[NSBundle mainBundle]loadNibNamed:@"GSYTableViewCell" owner:nil options:nil]lastObject];
}
@end

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容