SDWebImage源码解读 (一)

SDWebImage (v4.4.1)

特点:

  • Categories for UIImageView, UIButton, MKAnnotationView adding web image and cache management
  • An asynchronous image downloader - 异步下载
  • An asynchronous memory + disk image caching with automatic cache expiration handling - 缓存自动到期,memory和disk缓存
  • A background image decompression - 后台解压
  • A guarantee that the same URL won't be downloaded several times - URL不会被多次下载
  • A guarantee that bogus URLs won't be retried again and again - 虚假的URL不会被持续重试
  • A guarantee that main thread will never be blocked
    Performances! - 主线程安全
  • Use GCD and ARC

目录

  • 主要方法
  • SDWebImageOperation
  • SDWebImageCompat
  • Decoder
  • SDWebImageCodersManager
  • SDWebImageCoder
  • SDWebImageFrame
  • SDWebImageGIFCoder
  • SDWebImageWebPCoder
  • SDWebImageImageIOCoder
  • SDAnimatedImageRep
  • SDWebImageCoderHelper
  • Utils
  • SDWebImageManager
  • SDWebImagePrefetcher
  • SDWebImageTransition
  • Downloader
  • SDWebImageDownloader
  • SDWebImageDownloaderOperation
  • Cache
  • SDImageCache
  • SDImageCacheConfig
  • WebCacheCategories
  • NSImage+WebCache
  • NSButton+WebCache
  • MKAnnotationView+WebCache
  • UIView+WebCache
  • UIButton+WebCache
  • UIImageView+WebCache
  • UIImageView+HighlightedWebCache
  • Categories
  • NSData+ImageContentType
  • UIImage+WebP
  • UIView+WebCacheOperation
  • UIImage+ForceDecode
  • UIImage+GIF
  • UIImage+MultiFormat
  • FLAnimatedImage
  • FLAnimatedImageView+WebCache

主要类名及其作用

类名 功能
SDWebImageOperation 宏定义、常量、常用方法
SDWebImageCompat 统一cancel方法
SDWebImageCodersManager 总体入口
SDWebImageCoder 需要实现的接口
SDWebImageImageIOCoder PNG/JPEG的Coder操作
SDWebImageGIFCoder GIF的Coder操作
SDWebImageWebPCoder WebP的Coder操作
SDWebImageManager 管理cache和download入口
SDWebImagePrefetcher 预处理获取Image
SDWebImageTransition 过渡动画
SDWebImageDownloader 提供下载管理入口
SDWebImageDownloaderOperation 下载的Operation操作
SDImageCache 处理缓存逻辑
SDImageCacheConfig 配置缓存参数
NSData+ImageContentType 提供类型判断和ImageIO类型转换
UIImage+GIF Data转UIImage(GIF)扩展
UIImage+ForceDecode 解压操作
UIView+WebCacheOperation 提供顶层关于取消和下载记录的扩展

主要方法

SDWebImageOperation

这里代码不多,只实现了一个SDWebImageOperation协议。

@protocol SDWebImageOperation <NSObject>
- (void)cancel;

这里是为了安全和简洁。调用这个cancel方法,会使得它持有的两个operation(SDWebImageCombinedOperation和SDWebImageManager)都被cancel

SDWebImageCompat

这里在最开始先是使用OBJC_GC宏来告知使用者,SDWebimage是不支持垃圾回收机制的。
接着,作者在这里吐槽了一下Apple的平台判断宏。

 Apple's defines from TargetConditionals.h are a bit weird.
 Apple的从TargetConditionals.h引用的宏有点诡异。
 Seems like TARGET_OS_MAC is always defined (on all platforms).
 好像TARGET_OS_MAC 是在所有的平台上被默认(都是1)
 To determine if we are running on OSX, we can only rely on TARGET_OS_IPHONE=0 and all the other platforms
为了确定我们是否正在运行OSX,我们只能靠target_os_iphone = 0 和 其他所有的平台

这里我标注一下这个宏的槽点

 +------------------------------------------------+
 |                TARGET_OS_MAC                   |
 | +---+  +-------------------------------------+ |
 | |   |  |          TARGET_OS_IPHONE           | |
 | |OSX|  | +-----+ +----+ +-------+ +--------+ | |
 | |   |  | | IOS | | TV | | WATCH | | BRIDGE | | |
 | |   |  | +-----+ +----+ +-------+ +--------+ | |
 | +---+  +-------------------------------------+ |
 +------------------------------------------------+
 

接着就是一段冗长而又必要的平台判断。

枚举宏的细节

这里有一个很有意思的点是,作者把NS_ENUM和NS_OPTIONS的宏重写了一遍,改成了一样的位运算的样子。

#ifndef NS_ENUM
#define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type
#endif

#ifndef NS_OPTIONS
#define NS_OPTIONS(_type, _name) enum _name : _type _name; enum _name : _type
#endif

之所以这么写的原因是因为,普通的NS_ENUM只能使用在Objective-C下,在C++/Objective-C++下使用会出现问题。

主线程判断

dispatch_main_sync_safe宏是一个保证block在主线程执行的方法。
在之前的版本中,主线程判断使用的是NSThread来判断

#define dispatch_main_sync_safe(block)\
    if ([NSThread isMainThread]) {\
        block();\
    } else {\
        dispatch_sync(dispatch_get_main_queue(), block);\
    }

但是这个方法有个问题,它只能判断线程是否是主线程,但是无法判断队列是否是当前队列
现在更新了一个新的方法。

#ifndef dispatch_queue_async_safe
#define dispatch_queue_async_safe(queue, block)\
    if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(queue)) == 0) {\
        block();\
    } else {\
        dispatch_async(queue, block);\
    }
#endif

#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block) dispatch_queue_async_safe(dispatch_get_main_queue(), block)
#endif

strcmp()是c语言的字符串比较函数. strcmp(s1,s2) 判断两个字符串s1和s2是否相同,相同== 0; 如果当前线程已经是主线程了,那么在调用dispatch_async(dispatch_get_main_queue(), block)有可能会出现crash;如果当前线程不是主线程,调用dispatch_async(dispatch_get_main_queue(), block)
通过这样的判断,可以保证是主线程当前队列。
这么做的原因是可以查看这个。以及张星宇的这篇文章

修改图片尺寸

有时候,后台传给我们的图片是有多张的,需要去适配屏幕大小。
我们需要判断标识的比例来进行调整。

inline UIImage *SDScaledImageForKey(NSString * _Nullable key, UIImage * _Nullable image) {
    if (!image)
    {
        return nil;
    }
    
#if SD_MAC
    return image;
#elif SD_UIKIT || SD_WATCH
    //如果是动态图片,比如GIF图片,则迭代处理
    if ((image.images).count > 0)
    {
        NSMutableArray<UIImage *> *scaledImages = [NSMutableArray array];

        for (UIImage *tempImage in image.images) {
            [scaledImages addObject:SDScaledImageForKey(key, tempImage)];
        }
        
        UIImage *animatedImage = [UIImage animatedImageWithImages:scaledImages duration:image.duration];
        if (animatedImage) {
            animatedImage.sd_imageLoopCount = image.sd_imageLoopCount;
        }
        //把处理结束的图片再合成一张动态图片
        return animatedImage;
    }
    else
    {
#if SD_WATCH
        //判断screenScale方法是否存在
        if ([[WKInterfaceDevice currentDevice] respondsToSelector:@selector(screenScale)]) {
#elif SD_UIKIT
        //判断scale方法是否存在
        if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)])
        {
#endif
            CGFloat scale = 1;
            // “@2x.png”的长度为7,所以此处添加了这个判断,很巧妙
            if (key.length >= 8)
            {
                NSRange range = [key rangeOfString:@"@2x."];
                if (range.location != NSNotFound) {
                    scale = 2.0;
                }
                //这里需要判断图片的大小
                range = [key rangeOfString:@"@3x."];
                if (range.location != NSNotFound) {
                    scale = 3.0;
                }
            }
            // 使用initWithCGImage来根据Core Graphics的图片构建UIImage。
            UIImage *scaledImage = [[UIImage alloc] initWithCGImage:image.CGImage scale:scale orientation:image.imageOrientation];
            image = scaledImage;
        }
        return image;
    }
#endif
}

图片加载的顺序

从磁盘里获取图片

1.磁盘区拷贝数据到内核缓冲区;
2.内核缓冲区复制数据到用户控件;
3.生成UIImageView并赋值;
4.如果图像是未解码的PNG/JPEG,就解码为位图数据;
5.CATransation捕获到UIImageView layer树变化;
6.主线程提交CATranstion开始渲染;
7.若没有字节对齐,先进行字节对齐;
8.GPU处理位图数据,开始渲染。

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

推荐阅读更多精彩内容