美文网首页
iOS开发之SDwebImage的前世今生

iOS开发之SDwebImage的前世今生

作者: 英俊神武 | 来源:发表于2018-06-26 18:27 被阅读0次

    请你说说SDwebImage这个框架的内部实现原理?
    当你看到这句话的时候,是不是很熟悉,有可能你在博客上看到一些关于SDwebImage原理的详解,也大概记忆了一些标准的答案,但是小编今天要说的网上那些解读都太肤浅了,如果面试官是个高手,深入问几句,也许你就熄火了。
    首先我们要了解SDwebImage框架为什么出现?
    因为在一个App中多图片下载是一个耗时操作,也是消耗内存的操作,如果让下载过的图片不重复下载,当面临这些的问题,该如何解决?
    在SDwebImage这个框架还没有出现之前,一些比较优秀的互联网公司一些优秀App他们是怎么处理这个问题的呢?

    @interface ViewController ()
    /** tableView的数据源 */
    @property (nonatomic, strong) NSArray *apps;
    @end

    @implementation ViewController

    pragma mark ----------------------

    pragma mark lazy loading

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

        //字典数组
        NSArray *arrayM = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle]pathForResource:@"apps.plist" ofType:nil]];
        
        //字典数组---->模型数组
        NSMutableArray *arrM = [NSMutableArray array];
        for (NSDictionary *dict in arrayM) {
            [arrM addObject:[XMGAPP appWithDict:dict]];
        }
        _apps = arrM;
    }
    return _apps;
    

    }

    pragma mark ----------------------

    pragma mark UITableViewDatasource

    -(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对应的数据
    XMGAPP *appM = self.apps[indexPath.row];
    
    //2.2 设置标题
    cell.textLabel.text = appM.name;
    
    //2.3 设置子标题
    cell.detailTextLabel.text = appM.download;
    
    //2.4 设置图标
    NSURL *url = [NSURL URLWithString:appM.icon];
    NSData *imageData = [NSData dataWithContentsOfURL:url];
    UIImage *image = [UIImage imageWithData:imageData];
    cell.imageView.image = image;
    NSLog(@"%zd-----",indexPath.row);
    
    //3.返回cell
    return cell;
    

    }

    假设我们直接这样去下载图片,你可以尝试写个小个demo,它会存在两个问题:
    ①UI很不流畅
    ②图片重复下载

    ①当UI不流畅,我们应该怎么解决呢?
    可以开子线程下载图片,然后回到主线程刷新UI。

    ②图片重复下载,这个又该如何处理?
    先把之前已经下载过的图片保存起来,我们可以想到用一个字典讲它存储起来。这个时候又会存在存在一个问题,如果只是单纯用一个字典去存储,当App退出的时候,下次进入还是重新发起请求,因为你只是将图片进行内存缓存,当App退出的时候,就会释放掉。这个时候,我们不仅仅要做内存缓存,还要做磁盘缓存。

    这个时候我们完善后的代码是这样的

    import "ViewController.h"

    import "XMGAPP.h"

    @interface ViewController ()
    /** tableView的数据源 /
    @property (nonatomic, strong) NSArray apps;
    /
    内存缓存 /
    @property (nonatomic, strong) NSMutableDictionary images;
    /
    队列 /
    @property (nonatomic, strong) NSOperationQueue queue;
    /
    操作缓存 */
    @property (nonatomic, strong) NSMutableDictionary *operations;
    @end

    @implementation ViewController

    pragma mark ----------------------

    pragma mark lazy loading

    -(NSOperationQueue *)queue
    {
    if (_queue == nil) {
    _queue = [[NSOperationQueue alloc]init];
    //设置最大并发数
    _queue.maxConcurrentOperationCount = 5;
    }
    return _queue;
    }
    -(NSMutableDictionary *)images
    {
    if (_images == nil) {
    _images = [NSMutableDictionary dictionary];
    }
    return _images;
    }
    -(NSArray *)apps
    {
    if (_apps == nil) {

        //字典数组
        NSArray *arrayM = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle]pathForResource:@"apps.plist" ofType:nil]];
        
        //字典数组---->模型数组
        NSMutableArray *arrM = [NSMutableArray array];
        for (NSDictionary *dict in arrayM) {
            [arrM addObject:[XMGAPP appWithDict:dict]];
        }
        _apps = arrM;
    }
    return _apps;
    

    }

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

    pragma mark ----------------------

    pragma mark UITableViewDatasource

    -(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对应的数据
    XMGAPP *appM = self.apps[indexPath.row];
    
    //2.2 设置标题
    cell.textLabel.text = appM.name;
    
    //2.3 设置子标题
    cell.detailTextLabel.text = appM.download;
    
    //2.4 设置图标
    
    //先去查看内存缓存中该图片时候已经存在,如果存在那么久直接拿来用,否则去检查磁盘缓存
    //如果有磁盘缓存,那么保存一份到内存,设置图片,否则就直接下载
    //1)没有下载过
    //2)重新打开程序
    
    UIImage *image = [self.images objectForKey:appM.icon];
    if (image) {
        cell.imageView.image = image;
        NSLog(@"%zd处的图片使用了内存缓存中的图片",indexPath.row) ;
    }else
    {
        //保存图片到沙盒缓存
        NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
        //获得图片的名称,不能包含/
        NSString *fileName = [appM.icon lastPathComponent];
        //拼接图片的全路径
        NSString *fullPath = [caches stringByAppendingPathComponent:fileName];
        
        
        //检查磁盘缓存
        NSData *imageData = [NSData dataWithContentsOfFile:fullPath];
        //废除
        imageData = nil;
        
        if (imageData) {
            UIImage *image = [UIImage imageWithData:imageData];
            cell.imageView.image = image;
            
            NSLog(@"%zd处的图片使用了磁盘缓存中的图片",indexPath.row) ;
            //把图片保存到内存缓存
            [self.images setObject:image forKey:appM.icon];
    

    // NSLog(@"%@",fullPath);
    }else
    {
    //检查该图片时候正在下载,如果是那么久什么都捕捉,否则再添加下载任务
    NSBlockOperation *download = [self.operations objectForKey:appM.icon];
    if (download) {

            }else
            {
                
                //先清空cell原来的图片
                cell.imageView.image = [UIImage imageNamed:@"Snip20160221_306"];
                
                download = [NSBlockOperation blockOperationWithBlock:^{
                    NSURL *url = [NSURL URLWithString:appM.icon];
                    NSData *imageData = [NSData dataWithContentsOfURL:url];
                    UIImage *image = [UIImage imageWithData:imageData];
                    
                     NSLog(@"%zd--下载---",indexPath.row);
                    
                    //容错处理
                    if (image == nil) {
                        [self.operations removeObjectForKey:appM.icon];
                        return ;
                    }
                    //演示网速慢的情况
                    //[NSThread sleepForTimeInterval:3.0];
                
                    //把图片保存到内存缓存
                    [self.images setObject:image forKey:appM.icon];
                    
                    //NSLog(@"Download---%@",[NSThread currentThread]);
                    //线程间通信
                    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                        
                        //cell.imageView.image = image;
                        //刷新一行
                        [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft];
                        //NSLog(@"UI---%@",[NSThread currentThread]);
                    }];
                    
                    
                    //写数据到沙盒
                    [imageData writeToFile:fullPath atomically:YES];
                   
                    //移除图片的下载操作
                    [self.operations removeObjectForKey:appM.icon];
                    
                }];
                
                //添加操作到操作缓存中
                [self.operations setObject:download forKey:appM.icon];
                
                //添加操作到队列中
                [self.queue addOperation:download];
            }
            
        }
    }
    
    //3.返回cell
    return cell;
    

    }

    -(void)didReceiveMemoryWarning
    {
    [self.images removeAllObjects];

    //取消队列中所有的操作
    [self.queue cancelAllOperations];
    

    }

    //1.UI很不流畅 --- > 开子线程下载图片
    //2.图片重复下载 ---> 先把之前已经下载的图片保存起来(字典)
    //内存缓存--->磁盘缓存

    //3.图片不会刷新--->刷新某行
    //4.图片重复下载(图片下载需要时间,当图片还未完全下载之前,又要重新显示该图片)
    //5.数据错乱 ---设置占位图片

    /*
    Documents:会备份,不允许
    Libray
    Preferences:偏好设置 保存账号
    caches:缓存文件
    tmp:临时路径(随时会被删除)
    */

    这里小编认为有两个问题比较重要。
    ①下载这个过程是很重要,一些细心的面试官会问你多图片下载是怎么实现的?

    先判断放Operation的数组里,是否存在下载当前的任务,如果存在了就什么都不做,如果没有再初始化一个Operation任务,然后放到数组里。并且放到操作队列中去执行下载任务。当图片下载完了,把该Operation任务从该数组移除。

    ②面试官会问你,图片是如何进行内存缓存的?将图片存在字典里面,它的value是UIImage,但是key是什么呢?
    这个key是图片的url,比如一张图片的url是http://p16.qhimg.com/dr/48_48_/438ae9d2fbb.png,那么key一定是从这个url字符串去做文章,因为沙盒路径中/表现文件的层级关系,所以我们可以截图url字符串的后部分作为key就可以了。

    这个多图片的实现方式,差不多就是SDwebImage这个框架的前世。
    那么SDwebImage的今生已经很成熟,大家也用得很多,但是很多人可能只用过它来设置UIImageView,其实SDwebImage不仅可以设置UIImageView,还可以设置UIButton,并且可以播放.gif图片,或者你想单独拿到UIIamge也是可以的。

    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
    [self download];
    }

    //1.下载图片且需要获取下载进度
    //内存缓存&磁盘缓存
    -(void)download
    {
    [self.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://img4.duitang.com/uploads/blog/201310/18/20131018213446_smUw4.thumb.600_0.jpeg"] placeholderImage:[UIImage imageNamed:@"Snip20160221_306"] options:SDWebImageCacheMemoryOnly | SDWebImageProgressiveDownload progress:^(NSInteger receivedSize, NSInteger expectedSize) {

    } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
        
        switch (cacheType) {
            case SDImageCacheTypeNone:
                NSLog(@"直接下载");
                break;
            case SDImageCacheTypeDisk:
                NSLog(@"磁盘缓存");
                break;
            case SDImageCacheTypeMemory:
                NSLog(@"内存缓存");
                break;
            default:
                break;
        }
    }];
    
    NSLog(@"%@",[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]);
    

    }

    //2.只需要简单获得一张图片,不设置
    //内存缓存&磁盘缓存
    -(void)download2
    {
    [[SDWebImageManager sharedManager] downloadImageWithURL:[NSURL URLWithString:@"http://img4.duitang.com/uploads/blog/201310/18/20131018213446_smUw4.thumb.600_0.jpeg"] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
    NSLog(@"%f",1.0 * receivedSize / expectedSize);

    } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
        
        //得到图片
        self.imageView.image = image;
    }];
    

    }

    //3.不需要任何的缓存处理
    //没有做任何缓存处理|
    -(void)download3
    {
    //data:图片的二进制数据
    [[SDWebImageDownloader sharedDownloader] downloadImageWithURL:[NSURL URLWithString:@"http://img4.duitang.com/uploads/blog/201310/18/20131018213446_smUw4.thumb.600_0.jpeg"] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {

    } completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
        
        [[NSOperationQueue mainQueue]addOperationWithBlock:^{
             self.imageView.image = image;
        }];
       
    }];
    

    }

    //4.播放Gif图片
    -(void)gif
    {
    NSLog(@"%s",func);
    //self.imageView.image = [UIImage imageNamed:@"39e805d5ad6eddc4f80259d23bdbb6fd536633ca"];

    UIImage *image = [UIImage sd_animatedGIFNamed:@"麻雀"];
    self.imageView.image = image;
    

    }

    -(void)type
    {
    NSData *imageData = [NSData dataWithContentsOfFile:@"/Users/xiaomage/Desktop/Snip20160221_306.png"];
    NSString *typeStr = [NSData sd_contentTypeForImageData:imageData];
    NSLog(@"%@",typeStr);
    }

    SDwebImage中有一个关于options枚举,这个枚举功能很强大,假设后台给你URL图片地址,这个时候你将它缓存起来,但是URL的图片地址已经把图片更换了,这个启动APP你显示的还是以前那张图片,这个你可以设置SDwebImage中options的枚举值为SDWebImageRefreshCached,它可以刷新本地缓存。这一点很强大。

    SDwebImage跟普通多图片下载不同之处?
    ①这里SDwebImage跟上面多图片下载有一点不同的是,SDwebImage中的沙盒缓存图片的命名方式为对该图片的URL进行MD5加密,然后得到一个新的字符串设置为key的。
    ②多图片下载的时候我们用的NSDictionary字典进行缓存的,但是SDwebImage使用了NSCache类这个类进行缓存,NSCache这个类有一个属性totalCostLimit可以设置总成本数,如果总成本数是5 ,如果发现存的数据超过中成本那么会自动回收之前的对象,不需要我们手动移除。自动管理内存缓存数量和内存大小。

    import "ViewController.h"

    @interface ViewController ()<NSCacheDelegate>
    /** 注释 */
    @property (nonatomic, strong) NSCache *cache;
    @end

    @implementation ViewController

    -(NSCache *)cache
    {
    if (_cache == nil) {
    _cache = [[NSCache alloc]init];
    _cache.totalCostLimit = 5;//总成本数是5 ,如果发现存的数据超过中成本那么会自动回收之前的对象
    _cache.delegate = self;
    }
    return _cache;
    }

    //存数据

    • (IBAction)addBtnClick:(id)sender
      {
      //NSCache的Key只是对对象进行Strong引用,不是拷贝(和可变字典的区别)
      for (NSInteger i = 0; i<10; i++) {
      NSData *data = [NSData dataWithContentsOfFile:@"/Users/xiaomage/Desktop/Snip20160221_38.png"];

        //cost:成本
        [self.cache setObject:data forKey:@(i) cost:1];
        NSLog(@"存数据%zd",i);
      

      }
      }

    //取数据

    • (IBAction)checkBtnClick:(id)sender
      {
      NSLog(@"+++++++++++++++");
      for (NSInteger i = 0; i<10; i++) {
      NSData *data = [self.cache objectForKey:@(i)];
      if (data) {
      NSLog(@"取出数据%zd",i);
      }
      }
      }

    //删除数据

    • (IBAction)removeBtnClick:(id)sender
      {
      [self.cache removeAllObjects];
      }

    pragma mark ----------------------

    pragma mark NSCacheDelegate

    //即将回收对象的时候调用该方法
    -(void)cache:(NSCache *)cache willEvictObject:(id)obj
    {
    NSLog(@"回收%zd",[obj length]);
    }

    相关文章

      网友评论

          本文标题:iOS开发之SDwebImage的前世今生

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