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

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

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

推荐阅读更多精彩内容

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