SDWebImage源码学习

前言

  简单做个介绍,SDWebImage:以我个人的理解,就是帮我们异步加载图片,并且缓存这些图片到内存及disk的一款非常棒的开源框架。其实除此之外,还有很多强大的功能供我们使用,我们如果能够熟练使用其API 就可以实现很多复杂的需求了。

  github最新下载地址:https://github.com/rs/SDWebImage

  下面,就开始学习下该框架。

正文

  由于我下载的是最新版的,一切就按照该版本的来整理学习。

先看下一个方法:

[_imageView2 sd_setImageWithURL:url2 placeholderImage:image2 options:SDWebImageRetryFailed progress:^(NSInteger receivedSize, NSInteger expectedSize) {
        NSLog(@"对下载进度做相关处理的事情");
    } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
        NSLog(@"图片加载完成后做的一些事情");
    }];

  这是最全的一个方法,看SDWebImage的源码也可以看到很多缩小版的方法也都是直接调用了该方法。其他方法就不做记录了

  关于参数:1.sd_setImageWithURL: 图片的url

       2.placeholderImage: 占位图片(图片没加载完成时显示的图片,也可以不选择)

       3.options: 提供的一种加载机制选择。默认值:SDWebImageRetryFailed 。详细看下。       

   //失败后重试
     SDWebImageRetryFailed = 1 << 0, 

     //UI交互期间开始下载,导致延迟下载比如UIScrollView减速。
     SDWebImageLowPriority = 1 << 1,

     //只进行内存缓存
     SDWebImageCacheMemoryOnly = 1 << 2,

     //这个标志可以渐进式下载,显示的图像是逐步在下载
     SDWebImageProgressiveDownload = 1 << 3,

     //刷新缓存
     SDWebImageRefreshCached = 1 << 4,

     //后台下载
     SDWebImageContinueInBackground = 1 << 5,

     //NSMutableURLRequest.HTTPShouldHandleCookies = YES;

     SDWebImageHandleCookies = 1 << 6,

     //允许使用无效的SSL证书
     //SDWebImageAllowInvalidSSLCertificates = 1 << 7,

     //优先下载
     SDWebImageHighPriority = 1 << 8,

     //延迟占位符
     SDWebImageDelayPlaceholder = 1 << 9,

     //改变动画形象
     SDWebImageTransformAnimatedImage = 1 << 10,

      4.progress: 看名字和参数应该可以了解该block的作用

      5.completed: 图片加载完成后的回调block,参数里面说一下SDImageCacheType,也是个枚举,通过该枚举我们可以了解到加载的图片是从哪里加载(缓存)来的,提供三种:SDImageCacheTypeNone 没有缓存类型,即网络下载来的

             SDImageCacheTypeDisk 由硬盘缓存而来

             SDImageCacheTypeMemory 由内存缓存而来

接下来我们来看下内部实现:

 一、在这步之前先学习个基于runtime机制的“关联”,因为他里面很多地方都用到了。

  1.建立关联:

    方法:objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)

      介绍下参数:object 源对象

            key 关键字 唯一静态变量key 注意:该参数是一个指针

            value 关联的对象

            policy 关联策略 :表明了相关的对象是通过赋值,保留引用还是复制的方式进行关联的;还有这种关联是原子的还是非原子的。

      我个人的理解就是这个方法的作用就是:将value对象通过key关键字以及policy关联策略和object关联起来。(那么此时value对象的生命周期就是依据关联策略和object对象相挂钩的。比如SDWebImage里面用到的一个关联策略OBJC_ASSOCIATION_RETAIN_NONATOMIC。其他不用关心,看到RETAIN,就应该知道此时就算我们release了value对象,如果object对象没有被release的话,我们依然可以访问的到value对象的。再进一步说,就是在object对象存在的时候,value对象一定存在。除非我们采用取消关联的操作。)

  2.获取相关联的对象

    方法:objc_getAssociatedObject(id object, const void *key)

      参数就不介绍,对应上面的关联方法就可以知道了。但是说明一点,该方法是有返回值的,返回值为value类型的对象。

  总结,先说下理论的东西。关联对象的存在是解决拓展中无法添加属性值而存在的,它以一个全局字典的形式存在,索引是你传递过来的key。以这种方式模拟出符合面向对象的数据与操作绑定的现象。其实我个人认为,之所以在这里用这个,是因为大多数的图片请求都是批量进行的,反复的调用请求方法,如果没有一个类似全局的manager来管理这些请求,势必会造成非常被动以及性能消耗的结果。所以作者在这里采用了这种方式,会把manager管理的当前operation给cancel掉,减少不必要的负荷。来提高框架的性能。

 二、SDWebImage内部实现过程

  1.入口:UIImageView+WebCache中先通过“关联”取消当前正在队列中的其他下载操作,然后显示placeholder占位图片。方法:

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock 

  2.SDWebImageManager通过传进来的url等参数进行图片请求操作。方法:

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionWithFinishedBlock)completedBlock

  3.先对传进来的url进行判断,如果url为空,或者是之前已经请求过失败的url并且option是SDWebImageRetryFailed(失败后重新请求)的话,就会直接回调失败的completedBlock(nil, error, SDImageCacheTypeNone, YES, url); 并返回error信息(NSURLErrorFileDoesNotExist)。代码:

 1    BOOL isFailedUrl = NO;
 2     @synchronized (self.failedURLs) {
 3         isFailedUrl = [self.failedURLs containsObject:url];
 4     }
 5
 6     if (!url || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
 7         dispatch_main_sync_safe(^{
 8             NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil];
 9             completedBlock(nil, error, SDImageCacheTypeNone, YES, url);
10         });
11         return operation;
12     }

  4.通过urlKey在SDImageCache中先找看缓存里面图片是否已经下载。方法:

- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock

  5.会先在内存图片缓存中找,找到后回调doneBlock(image, SDImageCacheTypeMemory)到SDWebImageManager,SDWebImageManager再回调completedBlock(image, nil, cacheType, YES, url)到UIImageView+WebCache前端展示图片。代码:

1   UIImage *image = [self imageFromMemoryCacheForKey:key];
2   if (image) {
3      doneBlock(image, SDImageCacheTypeMemory);
4      return nil;
5   }

  6.如果在内存图片缓存中没有找到,生成NSOperation根据urlKey在硬盘中查找看图片是否已经缓存。如果从硬盘中读取到了图片,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存)。因为这一步是在 NSOperation 进行的操作,所以要回主线程进行结果回调doneBlock(diskImage, SDImageCacheTypeDisk)。然后SDWebImageManager再回调completedBlock(image, nil, cacheType, YES, url)到UIImageView+WebCache前端展示图片。代码:

 1     NSOperation *operation = [NSOperation new];
 2     dispatch_async(self.ioQueue, ^{
 3         if (operation.isCancelled) {
 4             return;
 5         }
 6
 7         @autoreleasepool {
 8             UIImage *diskImage = [self diskImageForKey:key];
 9             if (diskImage) {
10                 NSUInteger cost = SDCacheCostForImage(diskImage);
11                 [self.memCache setObject:diskImage forKey:key cost:cost];
12             }
13
14             dispatch_async(dispatch_get_main_queue(), ^{
15                 doneBlock(diskImage, SDImageCacheTypeDisk);
16             });
17         }
18     });

  7.如果内存和硬盘缓存都没有找到图片的话,就需要下载图片。调用SDWebImageDownloader的下载方法,到这一步才算是真正的下载。方法:

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock

  8.关于下载,真的是蛮复杂的,以目前的能力好难看下去。以下内容摘自网络,以后能看明白了再补。

    共享或重新生成一个下载器 SDWebImageDownloader 开始下载图片。图片下载由 NSURLConnection 来做,实现相关 delegate 来判断图片下载中、下载完成和下载失败。connection:didReceiveData: 中利用 ImageIO 做了按图片下载进度加载效果。connectionDidFinishLoading: 数据下载完成后交给 SDWebImageDecoder 做图片解码处理。图片解码处理在一个 NSOperationQueue 完成,不会拖慢主线程 UI。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多。在主线程 notifyDelegateOnMainThreadWithInfo: 宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo: 回调给 SDWebImageDownloader。imageDownloader:didFinishWithImage: 回调给 SDWebImageManager 告知图片下载完成。通知所有的 downloadDelegates 下载完成,回调给需要的地方展示图片。将图片保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存。写文件到硬盘也在以单独 NSInvocationOperation 完成,避免拖慢主线程。SDImageCache 在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片。SDWI 也提供了 UIButton+WebCache 和 MKAnnotationView+WebCache,方便使用。SDWebImagePrefetcher 可以预先下载图片,方便后续使用。

时间: 2024-10-31 10:38:12

SDWebImage源码学习的相关文章

SDWebImage源码学习笔记

//  这是我第二次学习sdwebimage源码,第一次学习吸收的很少,看不懂啊.第二次看个50%,在此记录一点笔记. 首先是目录: 1.SDWebImage目录 里面有两个类,SDWebImageCompat.h 里面有个根据屏幕设置图片scale的方法 SDWebImageOperation.h 声明了一个协议,取消操作 (可以理解这一个放的公共方法目录) 2.Downloader 目录(顾名思义,下载操作相关的目录)里面有两个关键的类 SDWebImageDownloaderOperati

【原】SDWebImage源码阅读(三)

[原]SDWebImage源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1.SDWebImageDownloader中的downloadImageWithURL 我们来到SDWebImageDownloader.m文件中,找到downloadImageWithURL函数.发现代码不是很长,那就一行行读.毕竟这个函数大概做什么我们是知道的.这个函数大概就是创建了一个SDWebImageSownloader的异步下载器,根据给定的URL下载image. 先映入眼帘的

【原】SDWebImage源码阅读(五)

[原]SDWebImage源码阅读(五) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 前面的代码并没有特意去讲SDWebImage的缓存机制,主要是想单独开一章节专门讲解缓存.之前我们也遇到一些缓存的属性和方法,比如storeImage.queryDiskCacheForKey.memCache等等. SDWebImage的缓存分为两个部分,一个内存缓存,使用NSCache实现,另一个就是硬盘缓存(disk),使用NSFileManager实现. 不过这么多函数,

【原】SDWebImage源码阅读(二)

[原]SDWebImage源码阅读(二) 本文转载请注明出处 —— polobymulberry-博客园 1. 解决上一篇遗留的坑 上一篇中对sd_setImageWithURL函数简单分析了一下,还留了一些坑.不过因为我们现在对这个函数有一个大概框架了,我们就按顺序一个个来解决. 首先是这一句代码: objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 就是给UIImageVi

FireMonkey 源码学习(5)

(5)UpdateCharRec 该函数的源码分析如下: procedure TTextLayoutNG.UpdateCharRec(const ACanvas: TCanvas; NeedBitmap: Boolean; var NewRec: PCharRec; HasItem: Boolean; const CharDic: TCharDic; const AFont: TFont; const Ch: UCS4Char; const NeedPath: Boolean = False);

jquery源码学习

jQuery 源码学习是对js的能力提升很有帮助的一个方法,废话不说,我们来开始学习啦 我们学习的源码是jquery-2.0.3已经不支持IE6,7,8了,因为可以少学很多hack和兼容的方法. jquery-2.0.3的代码结构如下 首先最外层为一个闭包, 代码执行的最后一句为window.$ = window.jquery = jquery 让闭包中的变量暴露倒全局中. 传参传入window是为了便于压缩 传入undefined是为了undifined被修改,他是window的属性,可以被修

Hadoop源码学习笔记(1) ——第二季开始——找到Main函数及读一读Configure类

Hadoop源码学习笔记(1) ——找到Main函数及读一读Configure类 前面在第一季中,我们简单地研究了下Hadoop是什么,怎么用.在这开源的大牛作品的诱惑下,接下来我们要研究一下它是如何实现的. 提前申明,本人是一直搞.net的,对java略为生疏,所以在学习该作品时,会时不时插入对java的学习,到时也会摆一些上来,包括一下设计模式之类的.欢迎高手指正. 整个学习过程,我们主要通过eclipse来学习,之前已经讲过如何在eclipse中搭建调试环境,这里就不多述了. 在之前源码初

HSQLDB源码学习——数据库安装启动及JDBC连接

HSQLDB 是一个轻量级的纯Java开发的开放源代码的关系数据库系统.因为HSQLDB的轻量(占用空间小),使用简单,支持内存运行方式等特点,HSQLDB被广泛用于开发环境和某些中小型系统中. 在http://sourceforge.net/projects/hsqldb/files/下载了HSQLDB 1.8.0版本.把下载的zip文件解压缩至任意目录例如c:\hsqldb1.8便完成安装. hsqldb有四种运行模式: 一.内存(Memory-Only)模式:所有数据都在内存里操作.应用程

lodash源码学习(10)

_.delay(func, wait, [args]) 延迟wait毫秒之后调用该函数,添加的参数为函数调用时的参数 //delay.js var baseDelay = require('./_baseDelay'),//baseDelay方法 baseRest = require('./_baseRest'),//创建使用rest参数方法 toNumber = require('./toNumber');//转化为数字 /** * * @param {Function} func 需要延迟执