SDWebImage源码解析

SDWebImage实现原理:

入口 setImageWithURL:placeholderImage:options: 会先把 placeholderImage 显示,然后 SDWebImageManager 根据 URL 开始处理图片。

进入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:,交给 SDImageCache 从缓存查找图片是否已经下载 queryDiskCacheForKey:delegate:userInfo:.

先从内存图片缓存查找是否有图片,如果内存中已经有图片缓存,SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager。

SDWebImageManagerDelegate 回调 webImageManager:didFinishWithImage: 到 UIImageView+WebCache 等前端展示图片。

如果内存缓存中没有,生成 NSInvocationOperation 添加到队列开始从硬盘查找图片是否已经缓存。

根据 URLKey 在硬盘缓存目录下尝试读取图片文件。这一步是在 NSOperation 进行的操作,所以回主线程进行结果回调 notifyDelegate:。

如果上一操作从硬盘读取到了图片,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存)。SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo:。进而回调展示图片。

如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调 imageCache:didNotFindImageForKey:userInfo:。

共享或重新生成一个下载器 SDWebImageDownloader 开始下载图片。

图片下载由 NSURLConnection 来做,实现相关 delegate 来判断图片下载中、下载完成和下载失败。

connection:didReceiveData: 中利用 ImageIO 做了按图片下载进度加载效果。

connectionDidFinishLoading: 数据下载完成后交给 SDWebImageDecoder 做图片解码处理。

图片解码处理在一个 NSOperationQueue 完成,不会拖慢主线程 UI。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多。

在主线程 notifyDelegateOnMainThreadWithInfo: 宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo: 回调给 SDWebImageDownloader。

imageDownloader:didFinishWithImage: 回调给 SDWebImageManager 告知图片下载完成。

通知所有的 downloadDelegates 下载完成,回调给需要的地方展示图片。

将图片保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存。写文件到硬盘也在以单独 NSInvocationOperation 完成,避免拖慢主线程。

SDImageCache 在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片。

SDWebImage的源码解析:

SD中重要的类:

SDWebImageDownloader:图片的下载SDWebImageManager:图片的管理 SDImageCache:图片的缓存

先讲解图片的缓存以及清理机制SDImageCache,源码比较简单,主要有以下几点:

1: 图片的存储,如果允许内存的缓存机制,先存储到内存的缓存中(NSCache),并给予图片成本cost。如果允许磁盘的缓存,再存储到磁盘的中。默认情况下两种缓存方式均为true。为了保持图片分辨率,需要对png和jpeg等图片采用对应的压缩机制,png有一个独特的签名,第一个8字节的PNG文件总是包含以下(十进制)值:137 80 78 71 13 10 26 10。

- (void)storeImage:(UIImage*)image recalculateFromImage:(BOOL)recalculate imageData:(NSData*)imageData forKey:(NSString*)key toDisk:(BOOL)toDisk {

if(!image || !key) {

return;

}

if(self.shouldCacheImagesInMemory) {

NSUIntegercost =SDCacheCostForImage(image);

[self.memCache setObject:image forKey:key cost:cost];

}

if(toDisk) {

dispatch_async(self.ioQueue, ^{

NSData*data = imageData;

if(image && (recalculate || !data)) {

#ifTARGET_OS_IPHONE

int alphaInfo =CGImageGetAlphaInfo(image.CGImage);

BOOLhasAlpha = !(alphaInfo == kCGImageAlphaNone ||

alphaInfo == kCGImageAlphaNoneSkipFirst ||

alphaInfo == kCGImageAlphaNoneSkipLast);

BOOLimageIsPng = hasAlpha;

if([imageData length] >= [kPNGSignatureData length]) {

imageIsPng =ImageDataHasPNGPreffix(imageData);

}

if(imageIsPng) {

data =UIImagePNGRepresentation(image);

}else{

data =UIImageJPEGRepresentation(image, (CGFloat)1.0);

}

#else

data = [NSBitmapImageReprepresentationOfImageRepsInArray:image.representations usingType:NSJPEGFileTypeproperties:nil];

#endif

}

[selfstoreImageDataToDisk:data forKey:key];

});

}

}

2:磁盘的缓存,把图片的url当成key,通过MD5加密,生成一个32位的16进制数转成一个唯一的字符串,来作为存储本地磁盘的文件名,沙河路径拼接此文件名就是图片存储在磁盘的唯一路径。

- (NSString*)cachedFileNameForKey:(NSString*)key {

const char *str = [keyUTF8String];

if(str ==NULL) {

str ="";

}

unsigned char r[CC_MD5_DIGEST_LENGTH];

CC_MD5(str, (CC_LONG)strlen(str), r);

NSString*filename = [NSStringstringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",

r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],

r[11], r[12], r[13], r[14], r[15], [[key pathExtension] isEqualToString:@""] ? @"": [NSStringstringWithFormat:@".%@", [key pathExtension]]];

returnfilename;

}

3:内存缓存,其实就是一个 NSCache,NSCache在系统内存紧张(较低)时,会自动释放对象,相当于可变字典,只不过多了cost参数而已。cost代表指定该key值对应的成本,用于计算记录在缓存中的所有对象的总成本。

if(self.shouldCacheImagesInMemory) {

NSUIntegercost =SDCacheCostForImage(image);

[self.memCache setObject:image forKey:key cost:cost];

}

4:查找缓存的过程,先查内存里(NSCache)有没有,有就返回,没有就继续查磁盘文件有没有,有的话先放到内存里,然后返回。用一个单独的队列来进行磁盘读写。

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

if(!doneBlock) {

returnnil;

}

if(!key) {

doneBlock(nil,SDImageCacheTypeNone);

returnnil;

}

UIImage*image = [selfimageFromMemoryCacheForKey:key];//先从NSCache内存的缓存中读取

if(image) {

doneBlock(image,SDImageCacheTypeMemory);// 如果读到直接block回去,并返回读取的位置

returnnil;

}

NSOperation*operation = [NSOperationnew];

//如果内存的缓存里没有读到的话,由于磁盘数据较多,避免线程的卡顿,需要开启单独的异步队列从磁盘中读取。

dispatch_async(self.ioQueue, ^{

if(operation.isCancelled) {

return;

}

@autoreleasepool{

UIImage*diskImage = [selfdiskImageForKey:key];//从磁盘中读取,key是图片链接,会根据第一步生成对应的唯一的文件名,从沙河中获取

if(diskImage &&self.shouldCacheImagesInMemory) {

NSUIntegercost =SDCacheCostForImage(diskImage);

[self.memCache setObject:diskImage forKey:key cost:cost];//如果从沙河中读取到图片,先将其存储到内存的缓存中,方便下次的读取

}

dispatch_async(dispatch_get_main_queue(), ^{

doneBlock(diskImage,SDImageCacheTypeDisk);//读取成功,返回主线程并block回去

});

}

});

returnoperation;

}

5: 图片的解码,从磁盘中读取时,以及从网络上下载完成图片时,都会先解码再返回

- (UIImage*)diskImageForKey:(NSString*)key {

NSData*data = [selfdiskImageDataBySearchingAllPathsForKey:key];//从磁盘里读取存储的data数据

if(data) {

UIImage*image = [UIImagesd_imageWithData:data];

image = [selfscaledImageForKey:key image:image];

if(self.shouldDecompressImages) {

image = [UIImagedecodedImageWithImage:image];//图片的解码,详见SDWebImageDecoder这个类

}

returnimage;

}else{

returnnil;

}

}

6:自动清理缓存的时机 ,无论是APP退到后台,或者APP被直接杀掉,都会进行图片的清理

[[NSNotificationCenterdefaultCenter] addObserver:self

selector:@selector(clearMemory)

name:UIApplicationDidReceiveMemoryWarningNotification

object:nil];

[[NSNotificationCenterdefaultCenter] addObserver:self

selector:@selector(cleanDisk)

name:UIApplicationWillTerminateNotification

object:nil];

[[NSNotificationCenterdefaultCenter] addObserver:self

selector:@selector(backgroundCleanDisk)

name:UIApplicationDidEnterBackgroundNotification

object:nil];

该方法里会检查图片的有效期,默认是7天,如果过期则删除。 用到了NSURLContentModificationDateKey这个key,表示文件的修改时间,多数情况下都是图片文件的创建时间,因为基本下载好了以后就不会去修改了。

staticconstNSIntegerkDefaultCacheMaxCacheAge =60*60*24*7;// 1 week

另如果你设置了最大的图片存储空间,那么系统也会在同一时间点做检查并清理。即使未过期,也会清理一些,按照文件创建的时间来排序做清理,更早创建的优先被清理。比较有意思的就是,清理工作会持续到图片只占你设定值的一半。

推荐阅读更多精彩内容

  • 技术无极限,从菜鸟开始,从源码开始。 由于公司目前项目还是用OC写的项目,没有升级swift 所以暂时SDWebI...
    充满活力的早晨阅读 12,205评论 0 2
  • 图片下载的这些回调信息存储在SDWebImageDownloader类的URLOperations属性中,该属性是...
    怎样m阅读 1,782评论 0 1
  • SDWebImage库总体分为这么几个部分: 类似UIImageView+WebCache这样的面向使用者的接口,...
    毅个天亮阅读 316评论 0 5
  • 我突然意识到,我的爸爸妈妈马上就要六十岁了,六十岁之后就是七十岁、八十岁、九十岁…… 即使他们健康长寿,能够成为百...
    悠焕阅读 150评论 0 1
  • 心态不同能带来很不一样的工作和生活状态。拿工作来说,先举一个积极的例子。那段时间自己状态不好的时候,恰逢我同办公室...
    六月花阅读 24评论 0 0
  • 【昨晚睡觉时间】23:45 【今日醒来时间】8:30 【今天开心的事】 【今天不满意的事】 感觉快好的病又要复发 ...
    涤生照海阅读 112评论 0 0
  • 就是画画
    Subwei阅读 190评论 0 0