多图下载-SDWebImage的使用和原理

多图下载

利用SDWebImage设置UIButton的图片

  • 正确用法
[button sd_setImageWithURL:[NSURL URLWithString:url] forState:UIControlStateNormal placeholderImage:image];

SDWebImage(多图下载框架)

(1)SDWebImage基本使用

    01 设置imageView的图片
    /*
     第一个参数:要下载图片的url
     第二个参数:占位图片
     第三个参数:下载的策略  kNilOptions表示采用默认
     第四个参数:progress 获取下载进度
     第五个参数:completed 下载完成
     */
    [cell.imageView sd_setImageWithURL:[NSURL URLWithString:app.icon] placeholderImage:[UIImage imageNamed:@"Snip20151102_160"] options:kNilOptions progress:^(NSInteger receivedSize, NSInteger expectedSize) {

        NSLog(@"%f",1.0* receivedSize/expectedSize);

    } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {

        NSLog(@"下载完成");
    }];

    02 设置图片并计算下载进度
       //下载并设置图片
    /*
     第一个参数:要下载图片的url地址
     第二个参数:设置该imageView的占位图片
     第三个参数:传一个枚举值,告诉程序你下载图片的策略是什么
     第一个block块:获取当前图片数据的下载进度
         receivedSize:已经下载完成的数据大小
         expectedSize:该文件的数据总大小
     第二个block块:当图片下载完成之后执行该block中的代码
         image:下载得到的图片数据
         error:下载出现的错误信息
         SDImageCacheType:图片的缓存策略(不缓存,内存缓存,沙盒缓存)
         imageURL:下载的图片的url地址
     */
    [cell.imageView sd_setImageWithURL:[NSURL URLWithString:app.icon] placeholderImage:[UIImage imageNamed:@"placehoder"] options:SDWebImageRetryFailed progress:^(NSInteger receivedSize, NSInteger expectedSize) {

        //计算当前图片的下载进度
        NSLog(@"%.2f",1.0 *receivedSize / expectedSize);

    } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {

    }];

    03 系统级内存警告如何处理(面试)
    //取消当前正在进行的所有下载操作
    //内部自动实现[[SDWebImageManager sharedManager].imageCache clearDisk]
    [[SDWebImageManager sharedManager] cancelAll];

    //清除缓存数据(面试)
    //cleanDisk:删除过期的文件数据,计算当前未过期的已经下载的文件数据的大小,如果发现该数据大小大于我们设置的最大缓存数据大小,那么程序内部会按照按文件数据缓存的时间从远到近删除,直到小于最大缓存数据为止。kDefaultCacheMaxCacheAge:默认缓存周期是一周

    //clearMemory:直接删除文件,重新创建新的文件夹
    
    //[[SDWebImageManager sharedManager].imageCache cleanDisk];
    [[SDWebImageManager sharedManager].imageCache clearMemory];

    04 SDWebImage默认的缓存时间是1周
    05 如何播放gif图片
    /*
    5-1 把用户传入的gif图片->NSData
    5-2 根据该Data创建一个图片数据源(NSData->CFImageSourceRef)
    5-3 计算该数据源中一共有多少帧,把每一帧数据取出来放到图片数组中
    5-4 根据得到的数组+计算的动画时间-》可动画的image
    [UIImage animatedImageWithImages:images duration:duration];
    */

    06 如何判断当前图片类型
    + (NSString *)sd_contentTypeForImageData:(NSData *)data;


(2)SDWebImage内部结构(面试)


1.png

1.请说明SDWebImage内部是如何进行缓存处理的?(缓存处理机制&缓存类&缓存时候键值对如何设置)答:
1)缓存机制:内部使用了内存缓存和磁盘缓存二级缓存机制,默认情况下在存储数据的时候,会先对图片进行内存缓存,然后做磁盘缓存。在读取图片数据的时候先检查内存缓存,如果不存在则再检查磁盘缓存。
2)缓存类:内部使用NSCache专门处理内存缓存。该类的使用方法和可变字典类似,是线程安全的,且具备自动回收清理的功能。
3)缓存时候键值对如何设置:把图片的URL作为KEY保存,把图片(UIimage)作为Value来保存。

SDWebImage底层基本原理:

  1. 实现功能及思路:
    0. cell多图片展示:通过NSData网络下载小图片
    1. 重复下载问题:设置任务缓存,每次下载前先判断
    2. 内存暴涨问题:图片二级缓存机制。
    3. 卡住主线程:在子线程中下载,下载完毕后回主线程刷新UI
    4. 图片显示错误:通过reloadRowsAtIndexPaths定点刷新UI
  1. 实现步骤:


    自定义NSOperation下载图片思路 – 有沙盒缓存.png
  • 实现细节:

      1.图片加载流程:
          1. 图片存在判断:先加载image,通过image是否为空判断,不为空,返回图片;不为空,通过另外方式加载,继续判断;如果通过路径是否存在,数组、字典包含元素等方式判断比较麻烦
    
          2. 加载顺序:图片缓存(一级缓存)加载--磁盘缓存(二级缓存)加载--先用占位图片显示,新开队列及任务下载图片
    
      2.缓存处理:
    
          1. 图片缓存外的图片在获取时都要写入图片缓存,在主线程中立即写入。即从沙盒中找到图片还是下载完图片后都要写入图片缓存中(一级缓存)
    
          2. 磁盘缓存外的图片,在下载完后要写入,由于写入操作耗时,可以在子线程中执行。(二级缓存)
    
          3. 图片缓存和磁盘缓存建议使用字典方式保存,Key值可以用图片的后缀名保存;保存前要对value值进行非空判断
    
          4. 磁盘缓存地址:为了方便下次使用,最好将数据写入沙盒中,方便以后直接使用。documents下面的文件会被备份,另外苹果官方严禁将下载的图片放到documents,弃之。library--perference保存偏好设置的,弃之;tmp中的文件会被随即删除,弃之。最终方案是放在library--cache中,不会备份,定期可删除
    
          5. 为避免重复下载,可设置任务缓存,每次创建新任务前先判断是否已经存在任务,若存在则等待;图片下载后(无论成功与失败)都应该清空任务缓存
    
      3.图片下载:
    
          1. 在下载图片前,主线程先用占位图片显示cell.imageView.image.
    
          2. 下载任务可以封装成一个方法来异步执行
    
          3. 先根据app.icon从任务缓存中加载任务,判断任务是否已经在operations中,若是,则等待下载完毕;否则再创建新的任务
    
          4. 小文件的下载直接通过NSData下载最好,使用NSURLSessionDownLoadTask-block还是会有点麻烦
    
          5. 网络请求非空判断:图片下载完毕后,在写入图片缓存前,需要进行非空判断,这是因为字典保存的value不能为空,所以当下载的图片为空时,要先移除操作缓存,并返回。移除操作缓存是因为不移除,下次就不会重新加载)
    
          6. ,最终下载完毕后需要实现:1.回到主线程刷新UI,并写入图片缓存;2.清除下载任务缓存;3.将图片写入到沙盒缓存
    
          7. 下载图片耗时,应该新开子线程来下载图片;可以通过NSOperationQueue来下载任务(懒加载非主队列),并设置最大并发数优化性能
    
      4. 主线程刷新UI
          1. 下载完毕后要回到主线程刷新UI。
    
          2. 由于cell的循环利用,所以刷新要通过reloadRowsAtIndexPaths
    
      5. 内存警告处理:
          1.将数据保存到字典中时,可能会收到内存警告,这时要情况所有内存图片和操作缓存,并停止队列,使程序得以保存)
    
  • 基本代码

###在vc.m中
@interface ViewController ()
/** tableView的数据源 */
@property (nonatomic ,strong)NSArray *apps;
/** 图片缓存*/
@property (nonatomic ,strong ,nonnull)NSMutableDictionary *images;
/** 任务缓存 */
@property (nonatomic ,strong ,nonnull)NSMutableDictionary *operations;
/** 队列 */
@property (nonatomic ,strong)NSOperationQueue *queue;
@end

@implementation ViewController

#pragma mark - lazy loading

-(NSArray *)apps
{
    if (_apps == nil) {

        //1.加载本地的plist文件
        NSArray *appplistArray = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle]pathForResource:@"apps.plist" ofType:nil]];

        //2.字典转模型 appplistArray(字典数组)---->模型数组
        NSMutableArray *arrayM = [NSMutableArray arrayWithCapacity:appplistArray.count];

        for (NSDictionary *dict in appplistArray) {
            [arrayM addObject:[FZQApp appWithDict:dict]];
        }

        _apps = arrayM;
    }
    return _apps;
}

-(NSMutableDictionary *)images
{
    if (_images == nil) {
        _images = [NSMutableDictionary dictionary];
    }
    return _images;
}

-(NSMutableDictionary *)operations
{
    if (_operations == nil) {
        _operations = [NSMutableDictionary dictionary];
    }
    return _operations;
}

-(NSOperationQueue *)queue
{
    if (_queue == nil) {
        //创建队列
        _queue = [[NSOperationQueue alloc]init];
        //设置最大并发数
        _queue.maxConcurrentOperationCount = 5;
    }
    return _queue;
}
#pragma mark ---------------------------
#pragma mark UITbaleViewDataSource

-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.apps.count;
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *ID = @"app";
    //1.创建cell
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    //2.设置cell的数据

    //2.1 取出该cell对应的数据
    FZQApp *app = self.apps[indexPath.row];

    //2.2 设置
    cell.textLabel.text = app.name;
    cell.detailTextLabel.text = app.download;
    cell.imageView.image = [self setUpImage:self.apps[indexPath.row] indexPath:indexPath];

    //3.返回cell
    return cell;
}


#pragma mark - Life Cycle
//收到内存警告时清除缓存和队列任务
-(void)didReceiveMemoryWarning
{
    self.images = nil;
    self.operations = nil;
    [self.queue cancelAllOperations];
}

@end


#pragma mark - Methods
/** 加载并设置图片 */
-(UIImage *)setUpImage:(FZQApp *)app indexPath:(NSIndexPath *)indexPath
{
    /********           缓存中查找图片          ********/
    //从缓存中获取图片
    UIImage *image = self.images[app.iconUrl];

    //判断图片是否已经存在,存在则直接显示
    if (image) return image;


    /********           磁盘缓存中查找图片          ********/
    //获取cache路径
    NSString *cachePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];

    //拼接图片磁盘缓存路径
    NSString *imgsPath = [cachePath stringByAppendingPathComponent:[app.iconUrl lastPathComponent]];

    //从磁盘中获取图片
    image = [UIImage imageWithContentsOfFile:imgsPath];

    //磁盘缓存中是否存在图片
    if(image){

            //存在写入到内存缓存中,并返回图片
            [self.images setObject:image forKey:app.iconUrl];

            return image;
    }

    /********           网络上加载图片          ********/
    //若不存在则到网上去下载图片,先用占位图片显示
    image = IMAGE(@"Snip20151102_160.png");

    //生成下载图片任务
    [self downLoadOperation:app indexPath:indexPath imagesPath:imgsPath];

    return image;
}

/* 设置下载任务 */
- (void)downLoadOperation:(FZQApp *)app indexPath:(NSIndexPath *)indexPath imagesPath:(NSString *)imagesPath
{
    //设置下载任务
    NSBlockOperation *downLoadOpe = self.operations[app.iconUrl];

    //判断下载任务是否存在
    if(downLoadOpe == nil){

        //不存在则新建下载任务,并记录下载任务
        downLoadOpe = [NSBlockOperation blockOperationWithBlock:^{

            //加载数据
            NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:app.iconUrl]];

            //下载图片
            UIImage *image = [UIImage imageWithData:data];

            //判断下载图片是否为空,若是需要清除下载任务并返回
            if (!image) {
                //清除任务
                [self.operations removeObjectForKey:app.iconUrl];

                //返回
                return ;
            }

            //回到主线程刷新UI
            [self refreshView:indexPath image:image];

            //写入内存缓存
            [self.images setObject:image forKey:app.iconUrl];

            //写入到磁盘缓存中
            [data writeToFile:imagesPath atomically:YES];

            //清除下载任务
            [self.operations removeObjectForKey:app.iconUrl];
        }];

        //添加任务
        [self.queue addOperation:downLoadOpe];

        // 记录下载任务
        [self.operations setObject:downLoadOpe forKey:app.iconUrl];
    }
}

/* 刷新UI */
- (void)refreshView:(NSIndexPath *)indexPath image:(UIImage *)image
{
    //下载完毕后回到主线程刷新数据
    [[NSOperationQueue mainQueue]addOperationWithBlock:^{

        //设置cell图片
        [self.tableView cellForRowAtIndexPath:indexPath].imageView.image = image;

        //刷新数据
        [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
    }];
}


###在模型.h中
@interface FZQApp : NSObject

/** 名称 */
@property (nonatomic ,strong)NSString *name;
/** 图片的url */
@property (nonatomic ,strong)NSString *icon;
/** 下载数量 */
@property (nonatomic ,strong)NSString *download;

+(instancetype)appWithDict:(NSDictionary *)dict;
@end

###在模型.m中
@implementation FZQApp

+(instancetype)appWithDict:(NSDictionary *)dict
{
    FZQApp *app = [[FZQApp alloc]init];
    //KVC
    [app setValuesForKeysWithDictionary:dict];

    return app;
}
@end

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,312评论 5 473
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,578评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,337评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,134评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,161评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,303评论 1 280
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,761评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,421评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,609评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,450评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,504评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,194评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,760评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,836评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,066评论 1 257
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,612评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,178评论 2 341

推荐阅读更多精彩内容