美文网首页工作生活
SDWebImage多图下载原理(简单易懂=Very棒)

SDWebImage多图下载原理(简单易懂=Very棒)

作者: iOS_Coder | 来源:发表于2019-07-04 16:40 被阅读0次

    多图下载

    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内部结构(面试)

    image

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

    SDWebImage底层基本原理:

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

    2. 实现步骤:

      image
    • 实现细节:

        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
    
    

    相关文章

      网友评论

        本文标题:SDWebImage多图下载原理(简单易懂=Very棒)

        本文链接:https://www.haomeiwen.com/subject/zvprhctx.html