iOS SDWebImage 学习

官方SDWebImage的架构图

SDWebImage库的作用:

通过对UIImageView的类别扩展来实现异步加载替换图片的工作。
主要用到的对象:

  1. UIImageView (WebCache)类别,入口封装,实现读取图片完成后的回调
  2. SDWebImageManager,对图片进行管理的中转站,记录哪些图片正在读取
    (1)向下层读取Cache(调用SDImageCache),或者向网络读取对象(调用SDWebImageDownloader)。 (2)实现SDImageCache和SDWebImageDownloader的回调
  3. SDImageCache
    (1)根据URL的MD5摘要对图片进行存储和读取(实现存在内存中或者存在硬盘上两种实现)
    (2)实现图片和内存清理工作
  4. SDWebImageDownloader,根据URL向网络读取数据(实现部分读取和全部读取后再通知回调两种方式)

SDWebImage 缓存流程

以最为常用的UIImageView为例:

  1. UIImageView+WebCache: setImageWithURL:placeholderImage:options: 先显示 placeholderImage ,同时由SDWebImageManager 根据 URL 来在本地查找图片。
  2. SDWebImageManager: downloadWithURL:delegate:options:userInfo: SDWebImageManager是将UIImageView+WebCache同SDImageCache链接起来的类, SDImageCache: queryDiskCacheForKey:delegate:userInfo:用来根据CacheKey查找图片是否已经在缓存中
  3. 如果内存中已经有图片缓存, SDWebImageManager会回调SDImageCacheDelegate : imageCache:didFindImage:forKey:userInfo:
  4. 而 UIImageView+WebCache 则回调SDWebImageManagerDelegate: webImageManager:didFinishWithImage:来显示图片。
  5. 如果内存中没有图片缓存,那么生成 NSInvocationOperation 添加到队列,从硬盘查找图片是否已被下载缓存。
  6. 根据 URLKey 在硬盘缓存目录下尝试读取图片文件。这一步是在 NSOperation 进行的操作,所以回主线程进行结果回调 notifyDelegate:。
  7. 如果上一操作从硬盘读取到了图片,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存)。SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo:。进而回调展示图片。
  8. 如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调 imageCache:didNotFindImageForKey:userInfo:。
  9. 共享或重新生成一个下载器 SDWebImageDownloader 开始下载图片。
  10. 图片下载由 NSURLSession 来做,实现相关 delegate 来判断图片下载中、下载完成和下载失败。
  11. connection:didReceiveData: 中利用 ImageIO 做了按图片下载进度加载效果。
  12. connectionDidFinishLoading: 数据下载完成后交给 SDWebImageDecoder 做图片解码处理。
  13. 图片解码处理在一个 NSOperationQueue 完成,不会拖慢主线程 UI。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多。
  14. 在主线程 notifyDelegateOnMainThreadWithInfo: 宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo: 回调给 SDWebImageDownloader。
  15. imageDownloader:didFinishWithImage: 回调给 SDWebImageManager 告知图片下载完成。
  16. 通知所有的 downloadDelegates 下载完成,回调给需要的地方展示图片。
  17. 将图片保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存。
  18. 写文件到硬盘在单独 NSInvocationOperation 中完成,避免拖慢主线程。
  19. 如果是在iOS上运行,SDImageCache 在初始化的时候会注册notification 到UIApplicationDidReceiveMemoryWarningNotification 以及 UIApplicationWillTerminateNotification,在内存警告的时候清理内存图片缓存,应用结束的时候清理过期图片。
  20. SDWebImagePrefetcher 可以预先下载图片,方便后续使用。

SDWebImage 使用

  • 查看缓存大小

    - (NSString *)readSDWebImageCache {
        NSUInteger size = [SDImageCache sharedImageCache].getSize;
        // 1k = 1024, 1m = 1024k
        if (size < 1024) { // 小于1k
            return [NSString stringWithFormat:@"%ldB",(long)size];
        }else if (size < 1024 * 1024) { // 小于1m
            CGFloat aFloat = size/1024;
            return [NSString stringWithFormat:@"%.0fK",aFloat];
        }else if (size < 1024 * 1024 * 1024) { // 小于1G
            CGFloat aFloat = size/(1024 * 1024);
            return [NSString stringWithFormat:@"%.1fM",aFloat];
        }else {
            CGFloat aFloat = size/(1024*1024*1024);
            return [NSString stringWithFormat:@"%.1fG",aFloat];
        }
    }
    复制代码
    
  • 清除缓存

    - (void)clearDisk {
        NSLog(@"SDWebImageCache---%@", [self readSDWebImageCache]);
        [[SDImageCache sharedImageCache] clearDiskOnCompletion:nil];
        [[SDImageCache sharedImageCache] clearMemory]; //可不写
        NSLog(@"SDWebImageCache2---%@", [self readSDWebImageCache]);
    }
    复制代码
    

SDWebImage 缓存

  • 清理缓存图片的策略:

    特别是最大缓存空间大小的设置。如果所有缓存文件的总大小超过这一大小,则会按照文件最后修改时间的逆序,以每次一半的递归来移除那些过早的文件,直到缓存的实际大小小于我们设置的最大使用空间。
    注意:它默认只支持超过7天的图片清除。不对图片缓存大小进行控制。当然它已经做了这种机制,只是maxCacheSize为默认值0,所以不生效。

    1. 遍历缓存目录使用下面函数
    NSArray *resourceKeys = @[NSURLIsDirectoryKey,   NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];
    
    NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
                                                includingPropertiesForKeys:resourceKeys                                                              options:NSDirectoryEnumerationSkipsHiddenFiles
                                                              errorHandler:NULL];
    复制代码
    
    1. 归档过期缓存
     for (NSURL *fileURL in fileEnumerator) {
         ......
         // 根据文件路径最后修改时间来获取内容
         NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];
     // 判断是否过缓存期
         if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate])         {
             [urlsToDelete addObject:fileURL];
             continue;
         }
    
         // 这里同时对未过期的文件根据文件大小进行归档,便以后续重置缓存.
         NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
         currentCacheSize += [totalAllocatedSize unsignedIntegerValue];
         [cacheFiles setObject:resourceValues forKey:fileURL];
     }
    复制代码
    
    1. 删除过期缓存
     for (NSURL *fileURL in urlsToDelete) {
         [_fileManager removeItemAtURL:fileURL error:nil];
     } 
    复制代码
    
    1. 重置缓存大小
    // 依据文件修改时间,对未过期的文件进行升序排序.
     NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
                                                     usingComparator:^NSComparisonResult(id obj1, id obj2) {
                                                         return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];
                                                     }];
    
     // 根据设定的缓存大小,对当前缓存进行调整,删除那些快过期的文件,使当前总的文件大小小与设定的缓存大小。
     for (NSURL *fileURL in sortedFiles) {
         if ([_fileManager removeItemAtURL:fileURL error:nil]) {
             NSDictionary *resourceValues = cacheFiles[fileURL];
             NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
             currentCacheSize -= [totalAllocatedSize unsignedIntegerValue];
    
             if (currentCacheSize < desiredCacheSize) {
                 break;
             }
         }
     }
    复制代码
    
  • app事件注册使用经典的观察者模式,当观察到内存警告、程序被终止、程序进入后台这些事件时,程序将自动调用相应的方法处理

    当收到系统内存告警通知时,对内存缓存进行处理

    [[NSNotificationCenter defaultCenter] addObserver:self
                                               selector:@selector(clearMemory)
                                                   name:UIApplicationDidReceiveMemoryWarningNotification
                                                 object:nil];
    
    复制代码
    

    当进程终止时,对缓存文件进行处理

    [[NSNotificationCenter defaultCenter] addObserver:self
                                               selector:@selector(cleanDisk)
                                                   name:UIApplicationWillTerminateNotification
                                                 object:nil];
    复制代码
    

    当进入后台运行时,对缓存文件进行处理

    [[NSNotificationCenter defaultCenter] addObserver:self
                                               selector:@selector(backgroundCleanDisk)
                                                   name:UIApplicationDidEnterBackgroundNotification
                                                 object:nil];
    复制代码
    

SDWebImage 源码解析

SDImageDownloader负责管理所有的下载任务,具体的下载任务由SDImageDownloaderOperation类负责。

  • SDWebImageDownloaderOperation

    开发者就可以不使用SDWebImage提供的下载任务类,而可以自定义相关类,只需要遵守协议即可,SDWebImageDownloaderOperation类也遵守了该协议,该类继承自 NSOperation 主要是为了将任务加进并发队列里实现多线程下载多张图片,真正实现下载操作的是 NSURLSessionTask 类的子类,这里就可以看出 SDWebImage 使用 NSURLSession 实现下载图片的功能

  • SDImageDownloader

    SDWebImage主要使用了自定义NSOperation子类,并在这个自定义NSOperation子类中通过一个可用的NSURLSession来创建一个执行服务器交互数据的NSURLSessionDataTask的下载任务,并由其全权负责下载工作,接着使用NSOperationQueue实现多线程的多图片下载。

其他

  • 常见SDWebImageOptions
    SDWebImageRetryFailed, 下载失败后会自动重新下载
    SDWebImageLowPriority, 当正在与UI进行交互时,自动暂停内部的一些下载功能
    SDWebImageRetryFailed | SDWebImageLowPriority,同时存在上边两种
    SDWebImageCacheMemoryOnly, 取消磁盘缓存只有内存缓存
    SDWebImageProgressiveDownload,默认情况,图像会在下载完成后一次性显示

  • 默认存储法都是是内存缓存和磁盘缓存结合的方式。如果你只需要内存缓存,那么在带options选项的方法options这里选择SDWebImageCacheMemoryOnly就可以了

  • 对于图片的缓存实际应用的是NSURLCache自带的cache机制。NSURLCache每次都要把缓存的raw data 再转化为UIImage

  • SDWebImage提供了如下三个category来进行缓存
    MKAnnotationView(WebCache)
    UIButton(WebCache)
    UIImageView(WebCache)

  • 比如在下载某个图片的过程中要响应一个事件,就覆盖这个方法:

    [[SDWebImageManager sharedManager].imageDownloader downloadImageWithURL:urlPath options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
       NSLog(@"下载进度---%f", (float)receivedSize/expectedSize);
    } completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) {
       NSLog(@"下载完成---%@", [NSThread currentThread]);
    }];
    复制代码
    
  • 图片下载速度不一致,用户快速滚动的时候,会因为cell重用导致图片混乱
    解决办法:MVC,使用模型保持下载的图像,再次刷新表格。

  • 将图像保存到模型里的优缺点
    优点:不用重复下载,利用MVC刷新表格,不会造成数据混乱,加载速度比较快
    缺点:内存。所有下载好的图像,都会记录在模型里。如果数据比较多(2000)造成内存警告

  • 图片格式简介
    PNG:无损压缩,压缩比较低,PNG图片一般会比JPG大。(GPU解压缩的消耗非常小,解压缩的速度比较快,比较清晰,苹果推荐使用)
    JPG:有损压缩!压缩比非常高!照相机使用(GPU解压缩的消耗非常大)
    GIF:动图
    BMP:位图,没有任何压缩,几乎不用

  • SDWebImage自己的编解码技术
    在展示一张图片的时候常使用imageNamed:这样的类方法去获取并展示这张图片,但是图片是以二进制的格式保存在磁盘或内存中的,如果要展示一张图片需要根据图片的不同格式去解码为正确的位图交由系统控件来展示,而解码的操作默认是放在主线程执行,凡是放在主线程执行的任务都务必需要考虑清楚,如果有大量图片要展示,就会在主线程中执行大量的解码任务,势必会阻塞主线程造成卡顿,所以SDWebImage自己实现相关的编解码操作,并在子线程中处理,就不会影响主线程的相关操作

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

推荐阅读更多精彩内容