品读优秀的第三方框架是最快的技术提升方式吧,在工作中,很多时候在思考如何封装一个功能组件时总会各种犹豫,为打破这种顾此失彼的状态,果断看一些源码,学习别人编程思想,并从中借鉴模仿其手法。 花了一周看这个框架顺便写个总结吧!
SDWebImage 源代码结构
SDWebImage 主要功能就是图片的异步下载+缓存,其次也包含了图片的解码和清除等;源码下载地址 SDWebImage Github 上源码(下载最新的 3.7.0 版本),下载后打开工程代码文件结构还是很清晰的,从文件命名初步分析整体如下图:
再从我们使用 SDWebImage 调用 API 入口入手一步步看调用到的方法和每个文件每个类,主要解析分为以下几类:
WebCache Categories 面向开发者接口层,开发者直接调用 API 接口异步加载图片,也就是说这是我们读代码的入口,入口方法中会调用管理类(协调调用下载和缓存等),例如 UIImageView+WebCache 类中的:
1 2
| - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options;
|
SDWebImageManager 是 Utils 管理工具类文件中,其是整个 SDWebImage 的核心负责发起下载和缓存,执行上步骤的方法后,上步骤方法会调用这个类的如下方法,在方法中调用下载和缓存的类;
1 2 3 4 5 6 7 8 9 10
| #import "SDWebImageOperation.h" #import "SDWebImageDownloader.h" #import "SDImageCache.h"
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;
|
SDImageCache 缓存对象,负责内存缓存和沙盒缓存的类,核心方法:
1 2
| - (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock;
|
SDWebImageDownloader 下载器也就是下载管理器,下载网络请求的前期设置 NSURLRequest对象请求头的封装、缓存、cookie的设置加载选项的处理及管理 Operation 之间的关系;SDWebImageDownloaderOperation 负责图片下载网络请求的具体操作;
1 2 3 4 5 6 7 8 9 10 11
| - (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock;
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response; - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response; - (void)connectionDidFinishLoading:(NSURLConnection *)aConnection;
|
运用到的其它工具类主要是:SDWebImageCompat 、SDWebImageDecoder、NSData+ImageContentType、UIImage+MultiFormat、UIImage+GIF、UIImage+WebP;
内部方法逻辑流程分析
SDWebImage 实现图片的加载、缓存处理、数据处理、图片下载等工作内部方法时序图:
- 创建 UIImageView 对象调用其分类 UIImageView+WebCache 的接口方法例如:
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder
(常用方法还有其它接口方法);
- UIImageView+WebCache 内部最终都会调用方法 (private method)
sd_setImageWithURL: placeholderImage: options: progress: completed:
;
- 上步方法内部实现首先判断需不需要取消当前图片的下载操作(cell 复用一个 UIImageView 多个下载会出现闪图),其调用的核心方法是 UIView+WebCacheOperation
- (void)sd_cancelImageLoadOperationWithKey:(NSString *)key
,这里思考为什么设计为 UIView 的分类,很好理解因为 UIButton 也要调用, 他们都是继承 UIView,其次调用者都是 self 对象本身;
- 第二步中内部方法中执行完第三步逻辑(判断完是否取消当前图片下载操作)后,继续执行,调用负责发起下载和缓存 SDWebImageManager 中的方法
downloadImageWithURL: options: progress: completed:
;
- SDWebImageManager 对象的上述方法执行过程中通过调用 SDImageCache 对象的
queryDiskCacheForKey: done:
方法去查询缓存相关操作,SDImageCache 对象活动完成后将结果返回;
- SDWebImageManager 对象的方法执行完判断缓存相关操作,继续执行调用 SDWebImageDownloader (下载器)对象的
downloadImageWithURL: options: progress: completed:
发起下载操作;具体的图片网络下载是由 SDWebImageDownloader 对象调用 SDWebImageDownloaderOperation 完成;
- 图片下载完成后图片结果 block 回调到 SDWebImageManager 对象中,SDWebImageManager 继续调用 SDImageCache 对象执行图片存储操作;
核心工具类分析
1. SDWebImageCompat 适配类
只有一个内联函数判断 @2x 和 @3x 图片判断处理操作, 核心代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
inline UIImage *SDScaledImageForKey(NSString *key, UIImage *image) { if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) { CGFloat scale = 1; 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; } }
UIImage *scaledImage = [[UIImage alloc] initWithCGImage:image.CGImage scale:scale orientation:image.imageOrientation]; image = scaledImage; }
return image; }
|
2. SDWebImageDecoder 解码
UIImage 的分类扩展一个类方法用于图片的解码操作,这里疑问是本来就是图片为何还要解码到 UIImage?图片的加载常使用 [UIImage imageNamed:@"xxx.png"]
,这种方式加载系统会把图像 Cache 到内存并且系统默认会在主线程对图片进行解码(系统自行完成 可看看 Core Graphics 框架),图像资源大或者图片多,这种方式会消耗很大的内存;解决这种问题可以使用 imageWithContentsOfFile:
图片加载方式,也可以将图片解码过程我们来把控提前在子线程中完成;
UIImage 是显示图片的高级方式,是不可变的,因而可安全的供线程使用;CGImage 是图片位图,是可变的,可调用 Core Graphics 层 API 改变 bitmaps 数据;CIImage 也包含图片相关数据的图片对象Core Image 层的不可变的;
3. NSData+ImageContentType 图片格式判断
NSData 的分类扩展一个类方法用于判断图片格式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
+ (NSString *)sd_contentTypeForImageData:(NSData *)data { uint8_t c; [data getBytes:&c length:1]; switch (c) { case 0xFF: return @"image/jpeg"; case 0x89: return @"image/png"; case 0x47: return @"image/gif"; case 0x49: case 0x4D: return @"image/tiff"; case 0x52: if ([data length] < 12) { return nil; }
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding]; if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) { return @"image/webp"; }
return nil; } return nil; }
|
4.UIImage+GIF 生成 GIF UIImage 对象
UIImage 分类扩展实现对GIF图片的 NSData 的处理,生成 GIF 格式的UIImage 对象,核心代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
|
+ (UIImage *)sd_animatedGIFWithData:(NSData *)data { if (!data) { return nil; }
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
size_t count = CGImageSourceGetCount(source);
UIImage *animatedImage;
if (count <= 1) { animatedImage = [[UIImage alloc] initWithData:data]; } else { NSMutableArray *images = [NSMutableArray array];
NSTimeInterval duration = 0.0f;
for (size_t i = 0; i < count; i++) { CGImageRef image = CGImageSourceCreateImageAtIndex(source, i, NULL); if (!image) { continue; }
duration += [self sd_frameDurationAtIndex:i source:source];
[images addObject:[UIImage imageWithCGImage:image scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp]];
CGImageRelease(image); }
if (!duration) { duration = (1.0f / 10.0f) * count; }
animatedImage = [UIImage animatedImageWithImages:images duration:duration]; }
CFRelease(source);
return animatedImage; }
+ (float)sd_frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source { float frameDuration = 0.1f; CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil); NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties; NSDictionary *gifProperties = frameProperties[(NSString *)kCGImagePropertyGIFDictionary];
NSNumber *delayTimeUnclampedProp = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime]; if (delayTimeUnclampedProp) { frameDuration = [delayTimeUnclampedProp floatValue]; } else {
NSNumber *delayTimeProp = gifProperties[(NSString *)kCGImagePropertyGIFDelayTime]; if (delayTimeProp) { frameDuration = [delayTimeProp floatValue]; } }
if (frameDuration < 0.011f) { frameDuration = 0.100f; }
CFRelease(cfFrameProperties); return frameDuration; }
|
5.UIImage+MultiFormat
实现 NSData 转 UIImage 对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
|
+ (UIImage *)sd_imageWithData:(NSData *)data { if (!data) { return nil; } UIImage *image; NSString *imageContentType = [NSData sd_contentTypeForImageData:data]; if ([imageContentType isEqualToString:@"image/gif"]) { image = [UIImage sd_animatedGIFWithData:data]; } #ifdef SD_WEBP else if ([imageContentType isEqualToString:@"image/webp"]) { image = [UIImage sd_imageWithWebPData:data]; } #endif else { image = [[UIImage alloc] initWithData:data]; UIImageOrientation orientation = [self sd_imageOrientationFromImageData:data]; if (orientation != UIImageOrientationUp) { image = [UIImage imageWithCGImage:image.CGImage scale:image.scale orientation:orientation]; } }
return image; }
+(UIImageOrientation)sd_imageOrientationFromImageData:(NSData *)imageData { UIImageOrientation result = UIImageOrientationUp; CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL); if (imageSource) { CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL); if (properties) { CFTypeRef val; int exifOrientation; val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation); if (val) { CFNumberGetValue(val, kCFNumberIntType, &exifOrientation); result = [self sd_exifOrientationToiOSOrientation:exifOrientation]; } CFRelease((CFTypeRef) properties); } else { } CFRelease(imageSource); } return result; }
|
tmp
- webp 图片格式
依赖 libwebp 库是 Google 出的图片库,他的好处就是比 jpg 图片格式压缩更厉害小省流量减轻服务器压力等;
- app 执行过程
app 执行过程堆栈分析最先调用的是 start 函数(系统内核准备工作动态库加载).dylid 这个库自身也是一个动态库但他的作用是动态链接器 dyld 内部会调用 _dyld_start()
函数 再调用 dyldbootstrap::start()
函数,执行了函数之后再走 main 函数,再开启 runloop 走完了之后再开启队列,然后再走 UIApplication 内部方法,再走 didfinsh 方法。
本文标题:SDWebImage 源码阅读解析一
文章作者:zerocc
发布时间:2016年06月03日
原始链接:http://www.zerocc.com.cn/20160603.html
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN 许可协议。转载请注明出处!