美文网首页v2panda的技术专题iOS进阶ios
iOS 图片加载框架-SDWebImage 解读

iOS 图片加载框架-SDWebImage 解读

作者: 要上班的斌哥 | 来源:发表于2016-03-09 22:40 被阅读50293次

    在 iOS 的图片加载框架中,SDWebImage 可谓是占据大半壁江山。它支持从网络中下载且缓存图片,并设置图片到对应的 UIImageView 控件或者 UIButton 控件。在项目中使用 SDWebImage 来管理图片加载相关操作可以极大地提高开发效率,让我们更加专注于业务逻辑实现。

    SDWebImage 概论

    1.提供了一个 UIImageView 的 category 用来加载网络图片并且对网络图片的缓存进行管理
    2.采用异步方式来下载网络图片
    3.采用异步方式,使用 memory+disk 来缓存网络图片,自动管理缓存。
    4.支持 GIF 动画
    5.支持 WebP 格式
    6.同一个 URL 的网络图片不会被重复下载
    7.失效的 URL 不会被无限重试
    8.耗时操作都在子线程,确保不会阻塞主线程
    9.使用 GCD 和 ARC
    10.支持 Arm64

    SDWebImage 使用

    1.使用 ImageView+WebCache category 来加载 UITableView 中 cell 的图片

    [cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder.png"]];
    

    2.使用 block,采用这个方案可以在网络图片加载过程中得知图片的下载进度和图片加载成功与否

    [cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder.png"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
        //... completion code here ... 
     }];
    

    3.使用 SDWebImageManager,SDWebImageManager 为UIImageView+WebCache category 的实现提供接口。

    SDWebImageManager *manager = [SDWebImageManager sharedManager] ;
    [manager downloadImageWithURL:imageURL options:0 progress:^(NSInteger   receivedSize, NSInteger expectedSize) { 
          // progression tracking code
     }  completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType,   BOOL finished, NSURL *imageURL) { 
       if (image) { 
        // do something with image
       }
     }];
    

    4.加载图片还有使用 SDWebImageDownloader 和 SDImageCache 方式,但那个并不是我们经常用到的。基本上面所讲的3个方法都能满足需求。

    SDWebImage 流程

    UIImageView的图片加载流程

    SDWebImage 接口

    SDWebImage 是一个成熟而且比较庞大的框架,但是在使用过程中并不需要太多的接口,这算是一种代码封装程度的体现。这里就介绍比较常用的几个接口。

    1. 给 UIImageView 设置图片的接口,SDWebImage 有提供多个给UIImageView 设置图片的接口,最终所有的接口都会调用下图的这个接口,这是大多数框架的做法。


      给UIImageView设置图片的接口
    2. 获取 SDWebImage 的磁盘缓存大小,在项目中有时候会需要统计应用的磁盘缓存内容大小,那么获取图片的缓存大小就是使用这个接口来实现

    [SDImageCache sharedImageCache] getSize];
    
    1. 清理内存缓存,清理内存中缓存的图片资源,释放内存资源。
    [[SDImageCache sharedImageCache] clearMemory];
    
    1. 有了清理内存缓存,自然也有清理磁盘缓存的接口
    [[SDImageCache sharedImageCache] clearDisk];
    

    SDWebImage 解析

    解析主要围绕着 SDWebImage 的图片加载流程来分析,介绍SDWebImage 这个框架加载图片过程中的一些处理方法和设计思路。

    1. 给 UIImageView 设置图片,然后 SDWebImage 调用这个最终的图片加载方法。


      1 给UIImageView设置图片
    2. 开始加载之前图片先取消对应的 UIImageView 先前的图片下载操作。试想,如果我们给 UIImageView 设置了一张新的图片,那么我们还会在意该 UIImageVIew 先前是要加载哪一张图片么?应该是不在意的吧!那是不是应该尝试把该 UIImageView 先前的加载图片相关操作给取消掉呢?

    [self sd_cancelCurrentImageLoad]
    
    2 取消对应的UIImageView先前的图片下载操作

    该方法经过周转,最后调用了以下方法,框架将图片对应的下载操作放到 UIView 的一个自定义字典属性 (operationDictionary) 中,取消下载操作第一步也是从这个 UIView 的自定义字典属性 (operationDictionary)中取出所有的下载操作,然后依次调用取消方法,最后将取消的操作从(operationDictionary) 字典属性中移除。


    最终的取消下载方法

    3.移除之前没用的图片下载操作之后就创建一个新的图片下载操作,然后设置到 UIView 的一个自定义字典属性 (operationDictionary) 中。


    3 创建一个新的图片下载操作

    4.看看如何创建一个新的图片下载操作,框架保存了一个失效的 url 列表,如果 url 失效了就会被加入这个列表,保证不会重复多次请求失效的 url。

    4 图片下载操作

    根据给定的 url 生成一个唯一的 key ,之后利用这个 key 到缓存中查找对应的图片缓存。


    查找图片缓存

    5.读取图片缓存,根据 key 先从内存中读取图片缓存,若没有命中内存缓存则读取磁盘缓存,如果磁盘缓存命中,那么将磁盘缓存读到内存中成为内存缓存。如果都没有命中缓存的话,那么就在执行的 doneBlock中开始下载图片。

    5 读取图片缓存

    6.图片下载操作完成后会将图片对应的数据通过 completedBlock 进行回调


    6 图片下载操作

    在图片下载方法中,调用了一个方法用于添加创建和下载过程中的各类block 回调。


    图片下载方法

    添加该 url 加载过程的状态回调 block


    状态回调Block

    如果该 url 是第一次加载的话,那么就会执行 createCallback 这个回调block ,然后在 createCallback 里面开始构建网络请求,在下载过程中执行各类进度 block 回调。


    构建网络请求

    7.当图片下载完成之后会回到 done 的 block 回调中做图片转换处理和缓存操作


    7 图片转换处理和缓存操作

    回到 UIImageView 控件的设置图片方法 block 回调中,给对应的UIImageView 设置图片,操作流程到此完成。


    Block中设置图片

    8.304 的处理
    SDWebImage在加载图片网络请求的 NSURLConnection 的代理中对httpCode 做了判断,当 httpCode 为 304 的时候放弃下载,读取缓存。


    304处理

    总结

    SDWebImage 作为一个优秀的图片加载框架,提供的使用方法和接口对开发者来说非常友好。其内部实现多是采用 block 的方式来实现回调,代码阅读起来可能没有那么直观。此文章旨在给大家讲解 SDWebImage 这个框架的图片大概加载流程,其中具体细节限于篇幅无法详细深究。能力有限,文章中难免有错误,若大家在阅读过程中有发现不合理或者错误的地方恳请在评论中指出,我会在第一时间进行修正,不胜感激。

    参考

    1. 注释项目 https://github.com/junbinchencn/SDWebImage
    2. https://github.com/rs/SDWebImage

    相关文章

      网友评论

      • 请叫我小白同学:原来给imagview 加载图片,会执行这么多步骤啊,受教了
        陈藩:@请叫我小白同学 同求
        请叫我小白同学:如果有大佬这份中文注释demo,更好了,能否给一份吗?
      • f7a50c298715:楼主讨论个问题
        // Apple's defines from TargetConditionals.h are a bit weird.
        // Seems like TARGET_OS_MAC is always defined (on all platforms).
        // To determine if we are running on OSX, we can only rely on TARGET_OS_IPHONE=0 and all the other platforms
        #if !TARGET_OS_IPHONE && !TARGET_OS_IOS && !TARGET_OS_TV && !TARGET_OS_WATCH
        #define SD_MAC 1
        #else
        #define SD_MAC 0
        #endif

        上面的这段宏定义换成

        #if !TARGET_OS_IPHONE
        #define SD_MAC 1
        #else
        #define SD_MAC 0
        #endif
        这样应该也是的吗?
      • PGOne爱吃饺子:楼主,可以给一个有注释的demo么,谢谢
        要上班的斌哥:https://github.com/junbinchencn/SDWebImage
      • PGOne爱吃饺子:感谢楼主分享了一份带有注释的代码,谢谢了啊
      • doudo:谢谢分享。有个关于session问题请教下,在下载图片的时候,downloader是单例,他的session属性自然也就是一个。下载队列queue里,每个operation中都有一个task下载任务,而这些task都是用的同一个session,并行下载的时候,是不是有问题呢。
      • 骑老虎喊救命:你好,情况是这样,我多加载一个cell,cell上总共加载1M左右的图片资源,但是内存使用情况一下飙升30M,会是什么原因
        2d2383806a31:试试,缓存tableview的hight。应该是tableview的优化没有做好
      • 加拉隆的深渊之核:楼主,SDWebimage 加载 网络的 gif 怎么搞呀? 升了4.0.0 就麻烦了
        加拉隆的深渊之核:@要上班的斌哥 好的,感谢大神
        要上班的斌哥:@大白李 升到 4.x 系列,采用 4.x 的 gif 图片加载方案,性能比 3.x 提升很多
      • 无忘无往:304哪里看的还是不很明白。注释中说304会返回缓存,但是在SDWebImage的304处理中没有看到有返回缓存?
        sea777777:@菜粉 304意思是,服务器告诉客户端,这个资源服务器没改动,用客户端原来的缓存就可以,这样避免重复下载!
      • 吃蘑菇De大灰狼:再次回来看,感觉每一步都比较清晰~
      • 楚简约:用SDWebImage缓存个人中心头像,更换头像清除了缓存,但是评论人头像再次请求服务器还是以前的头像, url地址一样(固定的),将option参数换成SDWebImageRefreshCached还是不行, 就是你说的第6点服务器那边传过来的url是一样的只是内容不一样,怎么破
        坦白从宽是犯罪:我好像以前碰到过这种情况 不刷新这行cell就好了
        一代骄马:@Blank_佐毅 应该是304那里吧。。
        AKyS佐毅:@楚_简书书_简约 内容不一样,应该生成新的链接,将老的删除。
      • e32950f79177:真心不错,加油
      • 楚简约:不走sd_setImageWithURL: placeholderImage: completed:^{ 代码块 } 里面代码块是什么原因
        2d2383806a31:需要先添加到视图。才会走代码块
      • 楚简约:不走sd_setImageWithURL: placeholderImage: completed:^{ 代码块 } 里面代码块是什么原因
      • WKCaesar:加载大图时程序崩溃怎么搞
      • 414697ada450:楼主, 如果是批量下载 图片, 有什么好的建议吗?


        414697ada450:@junbinchencn 批量图片下载

        要上班的斌哥:@burningHeart 批量图片下载,还是批量UIImageView加载图片?
      • tom555cat:想问一下,UIImageView+WebCache.m中的
        - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock
        这个方法在最后有个:
        [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];

        将operation放进字典之后,是从哪里执行这些operation方法的?
      • roast_duck:缓存查找后的doneBlock里面:if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
        后面这个怎么理解呢?代理不能响应或代理调用了方法,俩个不是相反的吗,是不是可以不写的?
        要上班的斌哥:@roast_duck (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) 这两个不是相反的,这个是一个逻辑或的判断技巧。试想是不是先要判断是否可以响应代理,如果不能响应代理,那么这个"||"判断是不是可以直接跳过后面的代理调用方法。如果可以响应代理的话,那么就需要代理调用方法了。
      • 76e402c3f7e5:很详细,受教了
      • ryugaku:马克 防面试
        pengxiaochao:@宇墨阳 :joy:
        宇墨阳:@要上班的斌哥 面试人A:我了解AFN sdImage YYKit 等等底层
        面试人B:我期望薪资是5k
        面试官:小A你的情况我已经了解了,我想上门汇报一下,明天给你答复。
        小B啊,你什么时候可以来上班呢?明天可以?最近项目比较赶!:joy:
        要上班的斌哥:@ryugaku :smile: 希望对你面试有帮助,
      • 2068e5e51f60:不错,写的很用力
        要上班的斌哥:@黄立波 :smile: 谢谢
      • tjfeng88:operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {
        // 如果内存缓存和磁盘缓存都没有找到,请求没被取消...,以下代码才会去尝试执行url对应的图片下载任务
        id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) {
        // 图片下载完成
        }
        }
        PGOne爱吃饺子:@BossKing10086 你好,你说的这个请求被取消是在哪里被取消的,谢谢
        tjfeng88:@junbinchen 还是楼主分析的比较详细 我也就踩在巨人的肩膀上面瞎倒腾 :grin:
        要上班的斌哥:@BossKing10086 :blush: 嗯,是的。还是你看的比较详细。我写的时候就大概写了个流程,具体的细节就没有全部列出来。 :smile:
      • VictoryForYou:请问304 怎么处理的
        要上班的斌哥:@VictoryForYou :smile: 评论没有办法上图,我在原文的末尾加了对304的补充
      • Nonnil:不错
        要上班的斌哥:@Nonnil 谢谢
      • Gaivn:感谢楼主辛苦整理,虽然一直在用,但是就是没有整理。
        要上班的斌哥:@Gavin_ldh :kissing_heart:
      • 75003cc5006c:楼主你好,写得非常好,可以分享一下你做了注释的那份代码吗?谢谢:heart_eyes:
        要上班的斌哥:@百变小bill http://pan.baidu.com/s/1o7ygbZs 百度网盘链接
        要上班的斌哥:@BossKing10086 http://pan.baidu.com/s/1o7ygbZs 百度网盘链接
        tjfeng88:@百变小bill 同问,请问能分享哈你做注释的那份代码吗?
      • 389c20d5a244:很详细

      本文标题:iOS 图片加载框架-SDWebImage 解读

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