36选7玩法 >iOS开发

iOS源码补完计划--SDWebImage4.0+源码参阅(附面试题/流程图)

2018-04-13 18:30 编辑: yyuuzhu 分类:iOS开发 来源:kirito_song

年也过完了、决定补完一下入行时就欠下的债。

参拜一下SDWebImage的源码。

并不是说一定要读如何如何、只是觉得源码的阅读是一种很好的学习方式。无论从架构还是技术点方面。


目录

  • 常见疑问(面试大全?)

    • 磁盘目录位于哪里?

    • 最大并发数、超时时长?

    • 图片如何命名?

    • 如何识别图片类型?

    • 所查找到的图片的来源?

    • 所有下载的图片都将被写入缓存?磁盘呢?何时缓存的?

    • 磁盘缓存的时长?清理操作的时间点?

    • 磁盘清理的原则?

    • 下载图片时、会使用缓存协议么?

    • 下载图片的URL必须是NSURL么?

    • 读取缓存以及读取磁盘的时候如何保证线程安全?

  • 相关知识点

    • NS_OPTIONS枚举与位运算

    • 内联函数

  • 准备工作

  • 工作原理

  • 业务层级

  • 核心代码(正常读取下载图片)

    • 最上层:UIView+WebCache

    • 逻辑层:SDWebImageManager

    • 业务层:

      • 缓存&&磁盘操作(SDImageCache)

      • 下载操作(SDWebImageDownloader)

  • 一些启发

    • 分层的接口API设计

    • 线程安全

    • 内联函数

    • 精细的缓存管理原则

    • 回调设计


常见疑问(面试大全?)

虽然我更推荐阅读源码、可如果实在没时间。这一段只要花费几分钟。
我还是比较喜欢把干货放在前面、方便伸手党(比如我)。
不过也不能保证涵盖全部问题、欢迎留言。

  • 磁盘目录位于哪里?

缓存在磁盘沙盒目录下Library/Caches
二级目录为~/Library/Caches/default/com.hackemist.SDWebImageCache.default

-?(instancetype)init?{?return?[self?initWithNamespace:@"default"];?//???~Library/Caches/default?}

-?(nonnull?instancetype)initWithNamespace:(nonnull?NSString?*)ns?{?NSString?*path?=?[self?makeDiskCachePath:ns];?return?[self?initWithNamespace:ns?diskCacheDirectory:path];
}

-?(nonnull?instancetype)initWithNamespace:(nonnull?NSString?*)ns
???????????????????????diskCacheDirectory:(nonnull?NSString?*)directory?{?if?((self?=?[super?init]))?{?NSString?*fullNamespace?=?[@"com.hackemist.SDWebImageCache."?stringByAppendingString:ns]?//?Init?the?disk?cache?if?(directory?!=?nil)?{
????????????_diskCachePath?=?[directory?stringByAppendingPathComponent:fullNamespace];
????????}?else?{?NSString?*path?=?[self?makeDiskCachePath:ns];
????????????_diskCachePath?=?path;
????????}?//??_diskCachePath?=?~/Library/Caches/default/com.hackemist.SDWebImageCache.default?}

你也可以通过[[SDImageCache sharedImageCache] addReadOnlyCachePath:bundledPath];来自定义一个路径。

但这个路径不会被存储使用、是给开发者自定义预装图片的路径。
  • 最大并发数、超时时长?

_downloadQueue?=?[NSOperationQueue?new];
_downloadQueue.maxConcurrentOperationCount?=?6;
_downloadTimeout?=?15.0;
  • 图片如何命名?

这里写入缓存和写入磁盘是不同的。
写入缓存时、直接用图片url作为key

//写入缓存?NSUInteger?cost?=?SDCacheCostForImage(image);
[self.memCache?setObject:image?forKey:key?cost:cost];

写入磁盘时、用url的MD5编码作为key??梢苑乐刮募?/p>

-?(nullable?NSString?*)cachedFileNameForKey:(nullable?NSString?*)key?{?const?char?*str?=?key.UTF8String;?if?(str?==?NULL)?{
????????str?=?"";
????}?unsigned?char?r[CC_MD5_DIGEST_LENGTH];
????CC_MD5(str,?(CC_LONG)strlen(str),?r);?NSURL?*keyURL?=?[NSURL?URLWithString:key];?NSString?*ext?=?keyURL???keyURL.pathExtension?:?key.pathExtension;?NSString?*filename?=?[NSString?stringWithFormat:@"xxxxxxxxxxxxxxxx%@",
??????????????????????????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],?ext.length?==?0???@""?:?[NSString?stringWithFormat:@".%@",?ext]];?return?filename;?//key?==?https://gss2.bdstatic.com/-fo3dSag_xI4khGkpoWK1HF6hhy/baike/c0%3Dbaike80%2C5%2C5%2C80%2C26/sign=034361ab922397ddc274905638ebd9d2/d31b0ef41bd5ad64dddebb.jpg;?//filename?==?f029945f95894e152771806785bc4f18.jpg;?}
  • 如何识别图片类型?

通过NSData数据的第一个字符进行判断。

+?(SDImageFormat)sd_imageFormatForImageData:(nullable?NSData?*)data?{?if?(!data)?{?return?SDImageFormatUndefined;
????}?//?File?signatures?table:?http://www.garykessler.net/library/file_sigs.html?uint8_t?c;
????[data?getBytes:&c?length:1];?switch?(c)?{?case?0xFF:?return?SDImageFormatJPEG;?case?0x89:?return?SDImageFormatPNG;?case?0x47:?return?SDImageFormatGIF;?case?0x49:?case?0x4D:?return?SDImageFormatTIFF;?case?0x52:?{?if?(data.length?>=?12)?{?//RIFF....WEBP?NSString?*testString?=?[[NSString?alloc]?initWithData:[data?subdataWithRange:NSMakeRange(0,?12)]?encoding:NSASCIIStringEncoding];?if?([testString?hasPrefix:@"RIFF"]?&&?[testString?hasSuffix:@"WEBP"])?{?return?SDImageFormatWebP;
????????????????}
????????????}?break;
????????}?case?0x00:?{?if?(data.length?>=?12)?{?//....ftypheic?....ftypheix?....ftyphevc?....ftyphevx?NSString?*testString?=?[[NSString?alloc]?initWithData:[data?subdataWithRange:NSMakeRange(4,?8)]?encoding:NSASCIIStringEncoding];?if?([testString?isEqualToString:@"ftypheic"]
????????????????????||?[testString?isEqualToString:@"ftypheix"]
????????????????????||?[testString?isEqualToString:@"ftyphevc"]
????????????????????||?[testString?isEqualToString:@"ftyphevx"])?{?return?SDImageFormatHEIC;
????????????????}
????????????}?break;
????????}
????}?return?SDImageFormatUndefined;
}
  • 所查找到的图片的来源?

typedef?NS_ENUM(NSInteger,?SDImageCacheType)?{?/**
?????*?从网上下载
????*/?SDImageCacheTypeNone,?/**
?????*?从磁盘获得
?????*/?SDImageCacheTypeDisk,?/**
?????*?从内存获得
?????*/?SDImageCacheTypeMemory
};
  • 所有下载的图片都将被写入缓存?磁盘呢?何时缓存的?

磁盘不是强制写入。从枚举SDWebImageOptions可见

typedef?NS_OPTIONS(NSUInteger,?SDWebImageOptions)?{?/**
?????*??禁用磁盘缓存
?????*/?SDWebImageCacheMemoryOnly?=?1?<

而Memory缓存应该是必须写入的(因为我并没找到哪里可以禁止)。
缓存的时间点、有两个(开发者也可以主动缓存)、且都是由SDWebImageManager进行。
其一是下载成功后、自动保存?;蛘呖⒄咄ü泶硗计⒎祷睾蠡捍?/p>

-?(nullable?UIImage?*)imageManager:(nonnull?SDWebImageManager?*)imageManager?transformDownloadedImage:(nullable?UIImage?*)image?withURL:(nullable?NSURL?*)imageURL;


=========>>SDWebImageManager?//获取转换用户后的图片?UIImage?*transformedImage?=?[self.delegate?imageManager:self?transformDownloadedImage:downloadedImage?withURL:url];?//用户处理成功?if?(transformedImage?&&?finished)?{?BOOL?imageWasTransformed?=?![transformedImage?isEqual:downloadedImage];?//用户处理的后若未生成新的图片、则保存下载的二进制文件。?//不然则由imageCache内部生成二进制文件保存?[self.imageCache?storeImage:transformedImage?imageData:(imageWasTransformed???nil?:?downloadedData)?forKey:key?toDisk:cacheOnDisk?completion:nil];
}

其二是当缓存中没有、但是从硬盘中查询到了图片。

@autoreleasepool?{?//搜索硬盘?NSData?*diskData?=?[self?diskImageDataBySearchingAllPathsForKey:key];?UIImage?*diskImage?=?[self?diskImageForKey:key];?//缓存到内存、默认为YES?if?(diskImage?&&?self.config.shouldCacheImagesInMemory)?{?NSUInteger?cost?=?SDCacheCostForImage(diskImage);?//使用NSChache缓存。?[self.memCache?setObject:diskImage?forKey:key?cost:cost];
????????}?if?(doneBlock)?{?dispatch_async(dispatch_get_main_queue(),?^{
???????????????doneBlock(diskImage,?diskData,?SDImageCacheTypeDisk);
?????????????});
????????}
}
  • 磁盘缓存的时长?清理操作的时间点?

默认为一周
static?const?NSInteger?kDefaultCacheMaxCacheAge?=?60?*?60?*?24?*?7;?//?1?week

能够以时间清除磁盘的方法为

-?(void)deleteOldFilesWithCompletionBlock:(nullable?SDWebImageNoParamsBlock)completionBlock;

调用的时机为

[[NSNotificationCenter?defaultCenter]?addObserver:self?selector:@selector(deleteOldFiles)
?????????????????????????????????????????????????????name:UIApplicationWillTerminateNotification?object:nil];
?[[NSNotificationCenter?defaultCenter]?addObserver:self?selector:@selector(backgroundDeleteOldFiles)
?????????????????????????????????????????????????????name:UIApplicationDidEnterBackgroundNotification?object:nil];

也就是当程序退出到后台、或者被杀死的时候。
这里、还有另外一个点。
Long-Running Task任务

-?(void)backgroundDeleteOldFiles?{
????Class?UIApplicationClass?=?NSClassFromString(@"UIApplication");?if(!UIApplicationClass?||?![UIApplicationClass?respondsToSelector:@selector(sharedApplication)])?{?return;
????}?UIApplication?*application?=?[UIApplication?performSelector:@selector(sharedApplication)];?//后台任务标识--注册一个后台任务?__block?UIBackgroundTaskIdentifier?bgTask?=?[application?beginBackgroundTaskWithExpirationHandler:^{?//超时(大概150秒?)自动结束后台任务?//结束后台任务?[application?endBackgroundTask:bgTask];
????????bgTask?=?UIBackgroundTaskInvalid;
????}];

????
????[self?deleteOldFilesWithCompletionBlock:^{?//结束后台任务?[application?endBackgroundTask:bgTask];
????????bgTask?=?UIBackgroundTaskInvalid;
????}];
}

正常程序在进入后台后、虽然可以继续执行任务。但是在时间很短内就会被挂起待机。
Long-Running可以让系统为app再多分配一些时间来处理一些耗时任务。

  • 磁盘清理的原则?

首先、通过时间进行清理。(最后修改时间>一周)
然后、根据占据内存大小进行清理。(如果占据内存大于上限、则按时间排序、删除到上限的1/2。)
这里我并没有看到使用频率优先级判断、所以应该是没有。

-?(void)deleteOldFilesWithCompletionBlock:(nullable?SDWebImageNoParamsBlock)completionBlock?{?//异步清理超时图片?dispatch_async(self.ioQueue,?^{?//获取磁盘目录?NSURL?*diskCacheURL?=?[NSURL?fileURLWithPath:self.diskCachePath?isDirectory:YES];?//NSURLIsDirectoryKey?判断是否为目录?//NSURLContentModificationDateKey?判断最后修改时间?//NSURLTotalFileAllocatedSizeKey?判断文件大小?NSArray?*resourceKeys?=?@[NSURLIsDirectoryKey,?NSURLContentModificationDateKey,?NSURLTotalFileAllocatedSizeKey];?//模具器--遍历磁盘路径下的文件?NSDirectoryEnumerator?*fileEnumerator?=?[_fileManager?enumeratorAtURL:diskCacheURL
???????????????????????????????????????????????????includingPropertiesForKeys:resourceKeys
??????????????????????????????????????????????????????????????????????options:NSDirectoryEnumerationSkipsHiddenFiles?errorHandler:NULL];?//计算一周前(需要释放)、的时间?NSDate?*expirationDate?=?[NSDate?dateWithTimeIntervalSinceNow:-self.config.maxCacheAge];?//保存缓存文件Dic?NSMutableDictionary?*cacheFiles?=?[NSMutableDictionary?dictionary];?//缓存总大小?NSUInteger?currentCacheSize?=?0;?//需要删除的url路径?NSMutableArray?*urlsToDelete?=?[[NSMutableArray?alloc]?init];?//遍历磁盘文件枚举器?for?(NSURL?*fileURL?in?fileEnumerator)?{?NSError?*error;?//获取每个文件所对应的三个参数(resourceKeys)?NSDictionary?*resourceValues?=?[fileURL?resourceValuesForKeys:resourceKeys?error:&error];?//?Skip?directories?and?errors.?if?(error?||?!resourceValues?||?[resourceValues[NSURLIsDirectoryKey]?boolValue])?{?//如果是文件夹则跳过?continue;
????????????}?//?Remove?files?that?are?older?than?the?expiration?date;?NSDate?*modificationDate?=?resourceValues[NSURLContentModificationDateKey];?if?([[modificationDate?laterDate:expirationDate]?isEqualToDate:expirationDate])?{?//如果时间超过指定日期、加入删除数组。跳过?[urlsToDelete?addObject:fileURL];?continue;
????????????}?//获取文件大小、并且把路径与大小存入字典。?//?Store?a?reference?to?this?file?and?account?for?its?total?size.?NSNumber?*totalAllocatedSize?=?resourceValues[NSURLTotalFileAllocatedSizeKey];
????????????currentCacheSize?+=?totalAllocatedSize.unsignedIntegerValue;
????????????cacheFiles[fileURL]?=?resourceValues;
????????}?//遍历删除文件?for?(NSURL?*fileURL?in?urlsToDelete)?{
????????????[_fileManager?removeItemAtURL:fileURL?error:nil];
????????}?//如果剩余文件大小仍超过阈值?//优先删除最老的文件?if?(self.config.maxCacheSize?>?0?&&?currentCacheSize?>?self.config.maxCacheSize)?{?//?Target?half?of?our?maximum?cache?size?for?this?cleanup?pass.?const?NSUInteger?desiredCacheSize?=?self.config.maxCacheSize?/?2;?//?将剩余的文件按修改时间排序?NSArray?*sortedFiles?=?[cacheFiles?keysSortedByValueWithOptions:NSSortConcurrent?usingComparator:^NSComparisonResult(id?obj1,?id?obj2)?{?return?[obj1[NSURLContentModificationDateKey]?compare:obj2[NSURLContentModificationDateKey]];
?????????????????????????????????????????????????????????????????????}];?//?删除文件?for?(NSURL?*fileURL?in?sortedFiles)?{?if?([_fileManager?removeItemAtURL:fileURL?error:nil])?{?NSDictionary?*resourceValues?=?cacheFiles[fileURL];?NSNumber?*totalAllocatedSize?=?resourceValues[NSURLTotalFileAllocatedSizeKey];
????????????????????currentCacheSize?-=?totalAllocatedSize.unsignedIntegerValue;?//直到低于阈值的二分之一?if?(currentCacheSize?
  • 下载图片时、会使用网络协议缓存逻辑么?

默认情况下不会、由以下代码可见。

NSURLRequestCachePolicy?cachePolicy?=?options?&?SDWebImageDownloaderUseNSURLCache???NSURLRequestUseProtocolCachePolicy?:?NSURLRequestReloadIgnoringLocalCacheData;?NSMutableURLRequest?*request?=?[[NSMutableURLRequest?alloc]?initWithURL:url
????????????????????????????????????????????????????????????????????cachePolicy:cachePolicy
????????????????????????????????????????????????????????????????timeoutInterval:timeoutInterval];

除非将options配置成SDWebImageDownloaderUseNSURLCache、否则每次都会从原地址重新下载、而不是用网络协议的缓存逻辑。

  • 下载图片的URL必须是NSURL么?

不是、在SDWebImageManager中有过容错处理。所以即便你传入一个字符串、依旧可以正确的查找。

if?([url?isKindOfClass:NSString.class])?{
????????url?=?[NSURL?URLWithString:(NSString?*)url];
????}?if?(![url?isKindOfClass:NSURL.class])?{
????????url?=?nil;
????}

但是由于API暴露出的是(nullable NSURL *)、如果你传入字符串、会有黄色警告


  • 读取缓存以及读取磁盘的时候如何保证线程安全?

  • 读取缓存
    读取缓存的时候是在主线程进行。由于使用NSCache进行存储、所以不需要担心单个value对象的线程安全。

  • 读取磁盘
    磁盘的读取虽然创建了一个NSOperation对象、但据我所见这个对象只是用来标记该操作是否被取消、以及取消之后不再读取磁盘文件的作用。
    真正的磁盘缓存是在另一个IO专属线程中的一个串行队列下进行的。
    如果你搜索self.ioQueue还能发现、不只是读取磁盘内容。
    包括删除、写入等所有磁盘内容都是在这个IO线程进行、以保证线程安全。
    但计算大小、获取文件总数等操作。则是在主线程进行。

_ioQueue?=?dispatch_queue_create("com.hackemist.SDWebImageCache",?DISPATCH_QUEUE_SERIAL);
==========>>>>><<<<<<===========?NSOperation?*operation?=?[NSOperation?new];?dispatch_async(self.ioQueue,?^{?if?(operation.isCancelled)?{?//?do?not?call?the?completion?if?cancelled?return;
????????}?@autoreleasepool?{?//搜索硬盘?NSData?*diskData?=?[self?diskImageDataBySearchingAllPathsForKey:key];?UIImage?*diskImage?=?[self?diskImageForKey:key];?//缓存到内存、默认为YES?if?(diskImage?&&?self.config.shouldCacheImagesInMemory)?{?NSUInteger?cost?=?SDCacheCostForImage(diskImage);?//使用NSChache缓存。?[self.memCache?setObject:diskImage?forKey:key?cost:cost];
????????????}?if?(doneBlock)?{?dispatch_async(dispatch_get_main_queue(),?^{
????????????????????doneBlock(diskImage,?diskData,?SDImageCacheTypeDisk);
????????????????});
????????????}
????????}
????});?return?operation;

相关知识点

如果对一些知识点不了解、可能对代码理解造成困扰。列举一下。

  • NS_OPTIONS枚举与位运算

上文中的SDWebImageOptions便是一个位移枚举

typedef?NS_OPTIONS(NSUInteger,?SDWebImageOptions)?{
????SDWebImageRetryFailed?=?1?<

和我们普通用的枚举

typedef?NS_ENUM(NSInteger,?SDImageCacheType)?{
????SDImageCacheTypeNone,
????SDImageCacheTypeDisk,
????SDImageCacheTypeMemory
};

从表面看有两点不同:

  • 枚举声明:NS_ENUM&& NS_OPTIONS
    其实从定义的效果上来讲、二者作用相同。
    更多的是语义化的角度。前者是普通枚举、后者是位移枚举。

  • 枚举中的位运算符号<<.
    位运算中、有三种基本运算符号.

  • 按位与"&"

只有对应的两个二进位均为1时,结果位才为1,否则为0
比如9&5,其实就是1001&0101=0001,因此9&5=1>二进制中,与1相&就保持原位,与0相&就为0

  • 按位或"|"

只要对应的二个二进位有一个为1时,结果位就为1,否则为0。
比如9|5,其实就是1001|0101=1101,因此9|5=13

  • 左移"<<"

把整数a的各二进位全部左移n位,高位丢弃,低位补0。左移n位其实就是乘以2的n次方。
例如1<<2 就是0001左移2为0100,因此1<<2=4

于是、在使用位移枚举的时候、我们就有了这种写法:
options:SDWebImageRetryFailed?|?SDWebImageCacheMemoryOnly];

上面的意思是。这个操作是如果失败了需要重试、并且只写入缓存。
其中options=SDWebImageRetryFailed | SDWebImageCacheMemoryOnly
也就是0b00000001| 0b00000100 = 0b00000101十进制中 = 5.

在内部判断时候就有了如下写法:
//是否磁盘缓存?BOOL?cacheOnDisk?=?!(options?&?SDWebImageCacheMemoryOnly);

等价于0101 & 0100 = 0100结果为真。
倘若

BOOL?lowPriority?=?!(options?&?SDWebImageLowPriority);

等价于0101 & 0010 = 0000结果为假。

  • 内联函数

在写入缓存时、出现了这样一行代码

NSUInteger?cost?=?SDCacheCostForImage(diskImage);
[self.memCache?setObject:diskImage?forKey:key?cost:cost];

其中SDCacheCostForImage指向一个静态内联函数

FOUNDATION_STATIC_INLINE?NSUInteger?SDCacheCostForImage(UIImage?*image)?{?#if?SD_MAC?return?image.size.height?*?image.size.width;?#elif?SD_UIKIT?||?SD_WATCH?return?image.size.height?*?image.size.width?*?image.scale?*?image.scale;?#endif?}

其中FOUNDATION_STATIC_INLINE作为宏指向static?inline、所以也等价于

static?__inline__?NSUInteger?SDCacheCostForImage(UIImage?*image)?{?#if?SD_MAC?return?image.size.height?*?image.size.width;?#elif?SD_UIKIT?||?SD_WATCH?return?image.size.height?*?image.size.width?*?image.scale?*?image.scale;?#endif?}

用宏写方法、我们都用过。但是表达式形式的宏定义有一定的弊端。(比如参数检查、越界等等)。

内联函数完全可以取代表达式形式的宏定义。

顺便谈谈为什么要用内联函数吧。

  • 效率来看

    • 函数之间调用,是内存地址之间的调用、当函数调用完毕之后还会返回原来函数执行的地址。函数调用将会有时间开销。

    • 内联函数在汇编中没有call语句。取消了函数的参数压栈

  • 相比表达式形式的宏定义

    • 需要预编译.因为inline内联函数也是函数、不需要预编译。

    • 调用时候会首先检查它的参数的类型、保证调用正确。

    • 可以使用所在类的?;こ稍奔八接谐稍?。

需要注意的是
  • 内联函数中尽量不要使用诸如循环语句等大量代码、可能会导致编译器放弃内联动作。

  • 内联函数的定义须在调用之前。


准备工作

随手下载了一个最新的?(4.2.3)

GitHub

PODS:
-?SDWebImage?(4.2.3):
-?SDWebImage/Core?(=?4.2.3)
-?SDWebImage/Core?(4.2.3)

DEPENDENCIES:
-?SDWebImage

SPEC?CHECKSUMS:
SDWebImage:?791bb72962b3492327ddcac4b1880bd1b5458431

PODFILE?CHECKSUM:?7fbc0b76fb4d0b0b2afa7d3a90b7bd68dea25abb

COCOAPODS:?1.3.1

工作原理

引用GitHub上一个导图


  • 1、外部API入口。
    通过UIImageView+WebCache的sd_setImageWithURL方法(等)作为入口来加载图片。

  • 2、内部API汇总。
    通过UIView+WebCache的'sd_internalSetImageWithURL'对UIImageView、UIButton 、MKAnnotationView中图片的下载请求进行汇总。

  • 3、开始加载图片。
    通过SDWebImageManager的loadImageWithURL对图片进行加载。

  • 4、查找本地
    通过SDImageCache的queryCacheOperationForKey查找缓存中是否存在图片。如果不存在再通过diskImageDataBySearchingAllPathsForKey进行磁盘搜索。

  • 5、返回本地图片给SDWebImageManager

  • 6、下载图片
    如果本地查询不到对应图片、则通过SDImageDownloader的downloadImage进行图片下载。

  • 7、下载完毕返回图片给SDWebImageManager

  • 8、由UIView+WebCache通过storeImage将下载图片保存本地

  • 9、返回图片给UIView+WebCache

  • 10、设置图片
    其中。


业务层级

  • 整个架构简单分为三层。

最上层:

负责业务的接入、图片的插入

#import?"UIImageView+WebCache.h"?#import?"UIButton+WebCache.h"?#import?"UIImageView+HighlightedWebCache.h"?//以及其汇总的?#import?"UIView+WebCache.h"

逻辑层

负责不同类型业务的分发。
读取(或写入)缓存(或磁盘)、下载等具体逻辑处理。

#import?"SDWebImageManager.h"

业务层

负责具体业务的实现

//缓存&&磁盘操作?#import?"SDImageCache.h"?//下载操作?#import?"SDWebImageDownloader.h"

当然、还有其他的工具类。但主要的、就是上面几个。


核心代码(正常读取下载图片)

  • 最上层:UIView+WebCache

所有的代码最终都会汇总到

#import?"UIView+WebCache.h"?/**
?*?@param?url????????????图片地址链接
?*?@param?placeholder????占位图
?*?@param?options????????下载图片的枚举。包括优先级、是否写入硬盘等
?*?@param?operationKey???一个记录当前对象正在加载操作的key、保证只有最新的操作在进行、默认为类名。
?????????????????????????所以如果你想下载多个图片并且都展示一下、可以尝试自定义几个operationKey来操作。(我猜)
?*?@param?setImageBlock??给开发者自定义set图片的callback
?*?@param?progressBlock??下载进度callback
?*?@param?completedBlock?下载完成的callback(sd已经给你set好了、只是会把图片给你罢了)
?*?@param?context????????一些额外的上下文字典。比如你可以搞一个专属的imageManager进来干活。
?*/?-?(void)sd_internalSetImageWithURL:(nullable?NSURL?*)url
??????????????????placeholderImage:(nullable?UIImage?*)placeholder
???????????????????????????options:(SDWebImageOptions)options
??????????????????????operationKey:(nullable?NSString?*)operationKey
?????????????????????setImageBlock:(nullable?SDSetImageBlock)setImageBlock
??????????????????????????progress:(nullable?SDWebImageDownloaderProgressBlock)progressBlock
?????????????????????????completed:(nullable?SDExternalCompletionBlock)completedBlock
???????????????????????????context:(nullable?NSDictionary?*)context?{?//以当前实例的class作为OperationKey?NSString?*validOperationKey?=?operationKey??:?NSStringFromClass([self?class]);?//清除当前OperationKey下正在进行的操作。节省无用功?[self?sd_cancelImageLoadOperationWithKey:validOperationKey];?//给对象实例绑定imageURLKey?=?url?objc_setAssociatedObject(self,?&imageURLKey,?url,?OBJC_ASSOCIATION_RETAIN_NONATOMIC);?//是否先加载占位图?if?(!(options?&?SDWebImageDelayPlaceholder))?{?if?([context?valueForKey:SDWebImageInternalSetImageGroupKey])?{
????????????dispatch_group_t?group?=?[context?valueForKey:SDWebImageInternalSetImageGroupKey];
????????????dispatch_group_enter(group);
????????}?//到主线城更新UI?dispatch_main_async_safe(^{?//set?占位图?[self?sd_setImage:placeholder?imageData:nil?basedOnClassOrViaCustomSetImageBlock:setImageBlock];
????????});
????}?if?(url)?{?//?小菊花?if?([self?sd_showActivityIndicatorView])?{
????????????[self?sd_addActivityIndicator];
????????}?//?允许开发者指定一个manager来进行操作?SDWebImageManager?*manager;?if?([context?valueForKey:SDWebImageExternalCustomManagerKey])?{
????????????manager?=?(SDWebImageManager?*)[context?valueForKey:SDWebImageExternalCustomManagerKey];
????????}?else?{
????????????manager?=?[SDWebImageManager?sharedManager];
????????}
????????
????????__weak?__typeof(self)wself?=?self;?id??operation?=?[manager?loadImageWithURL:url?options:options?progress:progressBlock?completed:^(UIImage?*image,?NSData?*data,?NSError?*error,?SDImageCacheType?cacheType,?BOOL?finished,?NSURL?*imageURL)?{?//图片下载||读取完成?__strong?__typeof?(wself)?sself?=?wself;?//小菊花?[sself?sd_removeActivityIndicator];?if?(!sself)?{?return;?}?BOOL?shouldCallCompletedBlock?=?finished?||?(options?&?SDWebImageAvoidAutoSetImage);?//是否不插入图片?//1、有图片、但是主动配置?//2、没图片、设置了延迟加载占位图?BOOL?shouldNotSetImage?=?((image?&&?(options?&?SDWebImageAvoidAutoSetImage))?||
??????????????????????????????????????(!image?&&?!(options?&?SDWebImageDelayPlaceholder)));
????????????SDWebImageNoParamsBlock?callCompletedBlockClojure?=?^{?//?if?(!sself)?{?return;?}?if?(!shouldNotSetImage)?{
????????????????????[sself?sd_setNeedsLayout];
????????????????}?if?(completedBlock?&&?shouldCallCompletedBlock)?{?//操作完成的回调?completedBlock(image,?error,?cacheType,?url);
????????????????}
????????????};?//?case?1a:?we?got?an?image,?but?the?SDWebImageAvoidAutoSetImage?flag?is?set?//?OR?//?case?1b:?we?got?no?image?and?the?SDWebImageDelayPlaceholder?is?not?set?if?(shouldNotSetImage)?{?//如果不显示图片、直接回调。?dispatch_main_async_safe(callCompletedBlockClojure);?return;
????????????}?/**自动插入图片***/?UIImage?*targetImage?=?nil;?NSData?*targetData?=?nil;?if?(image)?{?//?case?2a:?we?got?an?image?and?the?SDWebImageAvoidAutoSetImage?is?not?set?targetImage?=?image;
????????????????targetData?=?data;
????????????}?else?if?(options?&?SDWebImageDelayPlaceholder)?{?//?case?2b:?we?got?no?image?and?the?SDWebImageDelayPlaceholder?flag?is?set?targetImage?=?placeholder;
????????????????targetData?=?nil;
????????????}?if?([context?valueForKey:SDWebImageInternalSetImageGroupKey])?{
????????????????dispatch_group_t?group?=?[context?valueForKey:SDWebImageInternalSetImageGroupKey];
????????????????dispatch_group_enter(group);
????????????????dispatch_main_async_safe(^{
????????????????????[sself?sd_setImage:targetImage?imageData:targetData?basedOnClassOrViaCustomSetImageBlock:setImageBlock];
????????????????});?//?ensure?completion?block?is?called?after?custom?setImage?process?finish?dispatch_group_notify(group,?dispatch_get_main_queue(),?^{
????????????????????callCompletedBlockClojure();
????????????????});
????????????}?else?{
????????????????dispatch_main_async_safe(^{
????????????????????[sself?sd_setImage:targetImage?imageData:targetData?basedOnClassOrViaCustomSetImageBlock:setImageBlock];
????????????????????callCompletedBlockClojure();
????????????????});
????????????}
????????}];?//在读取图片之前。向正在进行加载的HashMap中加入当前operation?[self?sd_setImageLoadOperation:operation?forKey:validOperationKey];
????}?else?{
????????dispatch_main_async_safe(^{
????????????[self?sd_removeActivityIndicator];?if?(completedBlock)?{?NSError?*error?=?[NSError?errorWithDomain:SDWebImageErrorDomain?code:-1?userInfo:@{NSLocalizedDescriptionKey?:?@"Trying?to?load?a?nil?url"}];
????????????????completedBlock(nil,?error,?SDImageCacheTypeNone,?url);
????????????}
????????});
????}
}

一个简单的流程图


UIView+WebCache流程图
  • 逻辑层:SDWebImageManager

SDWebImage中最核心的类、调度这图片的下载(SDWebImageDownloader)以及缓存(SDImageCache)。

此外、SDWebImageManager并不依托于UIView+WebCache、完全可以单独使用。
-?(id?)loadImageWithURL:(nullable?NSURL?*)url
?????????????????????????????????????options:(SDWebImageOptions)options
????????????????????????????????????progress:(nullable?SDWebImageDownloaderProgressBlock)progressBlock
???????????????????????????????????completed:(nullable?SDInternalCompletionBlock)completedBlock?{?NSAssert(completedBlock?!=?nil,?@"If?you?mean?to?prefetch?the?image,?use?-[SDWebImagePrefetcher?prefetchURLs]?instead");?//所以、我们并不需要在外部把字符串变为NSURL。?if?([url?isKindOfClass:NSString.class])?{
????????url?=?[NSURL?URLWithString:(NSString?*)url];
????}?if?(![url?isKindOfClass:NSURL.class])?{
????????url?=?nil;
????}?//下载操作的对象?__block?SDWebImageCombinedOperation?*operation?=?[SDWebImageCombinedOperation?new];
????__weak?SDWebImageCombinedOperation?*weakOperation?=?operation;?BOOL?isFailedUrl?=?NO;?if?(url)?{?@synchronized?(self.failedURLs)?{?//线程安全?isFailedUrl?=?[self.failedURLs?containsObject:url];
????????}
????}?//url为空?||?(未设置失败重试?&&?这个url已经失败过)?if?(url.absoluteString.length?==?0?||?(!(options?&?SDWebImageRetryFailed)?&&?isFailedUrl))?{?//发出一个获取失败的回调?[self?callCompletionBlockForOperation:operation?completion:completedBlock?error:[NSError?errorWithDomain:NSURLErrorDomain?code:NSURLErrorFileDoesNotExist?userInfo:nil]?url:url];?return?operation;
????}?//将操作添加到正在进行的操作数池?@synchronized?(self.runningOperations)?{
????????[self.runningOperations?addObject:operation];
????}?//默认就是url作为key、也可以自定义mananger的相关block?NSString?*key?=?[self?cacheKeyForURL:url];?//通过key、查找本地图片?operation.cacheOperation?=?[self.imageCache?queryCacheOperationForKey:key?done:^(UIImage?*cachedImage,?NSData?*cachedData,?SDImageCacheType?cacheType)?{?if?(operation.isCancelled)?{?//操作被取消、移除操作池?[self?safelyRemoveOperationFromRunning:operation];?return;
????????}?//本地没有图片?||?刷新缓存?if?((!cachedImage?||?options?&?SDWebImageRefreshCached)?&&?(![self.delegate?respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)]?||?[self.delegate?imageManager:self?shouldDownloadImageForURL:url]))?{?//有本地图片。但需要被刷新?if?(cachedImage?&&?options?&?SDWebImageRefreshCached)?{?//先回调出去本地图片。再继续下载操作?[self?callCompletionBlockForOperation:weakOperation?completion:completedBlock?image:cachedImage?data:cachedData?error:nil?cacheType:cacheType?finished:YES?url:url];
????????????}?//下面是根据调用者传进来的option,来匹配设置了哪些,就给downloaderOptions赋值哪些option?SDWebImageDownloaderOptions?downloaderOptions?=?0;?if?(options?&?SDWebImageLowPriority)?downloaderOptions?|=?SDWebImageDownloaderLowPriority;?if?(options?&?SDWebImageProgressiveDownload)?downloaderOptions?|=?SDWebImageDownloaderProgressiveDownload;?if?(options?&?SDWebImageRefreshCached)?downloaderOptions?|=?SDWebImageDownloaderUseNSURLCache;?if?(options?&?SDWebImageContinueInBackground)?downloaderOptions?|=?SDWebImageDownloaderContinueInBackground;?if?(options?&?SDWebImageHandleCookies)?downloaderOptions?|=?SDWebImageDownloaderHandleCookies;?if?(options?&?SDWebImageAllowInvalidSSLCertificates)?downloaderOptions?|=?SDWebImageDownloaderAllowInvalidSSLCertificates;?if?(options?&?SDWebImageHighPriority)?downloaderOptions?|=?SDWebImageDownloaderHighPriority;?if?(options?&?SDWebImageScaleDownLargeImages)?downloaderOptions?|=?SDWebImageDownloaderScaleDownLargeImages;?if?(cachedImage?&&?options?&?SDWebImageRefreshCached)?{?//?force?progressive?off?if?image?already?cached?but?forced?refreshing?downloaderOptions?&=?~SDWebImageDownloaderProgressiveDownload;?//?ignore?image?read?from?NSURLCache?if?image?if?cached?but?force?refreshing?downloaderOptions?|=?SDWebImageDownloaderIgnoreCachedResponse;
????????????}?//下载图片?SDWebImageDownloadToken?*subOperationToken?=?[self.imageDownloader?downloadImageWithURL:url?options:downloaderOptions?progress:progressBlock?completed:^(UIImage?*downloadedImage,?NSData?*downloadedData,?NSError?*error,?BOOL?finished)?{
????????????????__strong?__typeof(weakOperation)?strongOperation?=?weakOperation;?if?(!strongOperation?||?strongOperation.isCancelled)?{?//?Do?nothing?if?the?operation?was?cancelled?//?See?#699?for?more?details?//?if?we?would?call?the?completedBlock,?there?could?be?a?race?condition?between?this?block?and?another?completedBlock?for?the?same?object,?so?if?this?one?is?called?second,?we?will?overwrite?the?new?data?}?else?if?(error)?{
????????????????????[self?callCompletionBlockForOperation:strongOperation?completion:completedBlock?error:error?url:url];?if?(???error.code?!=?NSURLErrorNotConnectedToInternet?&&?error.code?!=?NSURLErrorCancelled?&&?error.code?!=?NSURLErrorTimedOut?&&?error.code?!=?NSURLErrorInternationalRoamingOff?&&?error.code?!=?NSURLErrorDataNotAllowed?&&?error.code?!=?NSURLErrorCannotFindHost?&&?error.code?!=?NSURLErrorCannotConnectToHost?&&?error.code?!=?NSURLErrorNetworkConnectionLost)?{?@synchronized?(self.failedURLs)?{?//失败记录?[self.failedURLs?addObject:url];
????????????????????????}
????????????????????}
????????????????}?else?{?if?((options?&?SDWebImageRetryFailed))?{?//失败重新下载?@synchronized?(self.failedURLs)?{?//从失败记录移除?[self.failedURLs?removeObject:url];
????????????????????????}
????????????????????}?//是否磁盘缓存?BOOL?cacheOnDisk?=?!(options?&?SDWebImageCacheMemoryOnly);?if?(self?!=?[SDWebImageManager?sharedManager]?&&?self.cacheKeyFilter?&&?downloadedImage)?{?//缩放?downloadedImage?=?[self?scaledImageForKey:key?image:downloadedImage];
????????????????????}?if?(options?&?SDWebImageRefreshCached?&&?cachedImage?&&?!downloadedImage)?{?//是否需要转换图片?//成功下载图片、自定义实现了图片处理的代理?}?else?if?(downloadedImage?&&?(!downloadedImage.images?||?(options?&?SDWebImageTransformAnimatedImage))?&&?[self.delegate?respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)])?{?dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,?0),?^{?//获取转换用户后的图片?UIImage?*transformedImage?=?[self.delegate?imageManager:self?transformDownloadedImage:downloadedImage?withURL:url];?//用户处理成功?if?(transformedImage?&&?finished)?{?BOOL?imageWasTransformed?=?![transformedImage?isEqual:downloadedImage];?//用户处理的后若未生成新的图片、则保存下载的二进制文件。?//不然则由imageCache内部生成二进制文件保存?[self.imageCache?storeImage:transformedImage?imageData:(imageWasTransformed???nil?:?downloadedData)?forKey:key?toDisk:cacheOnDisk?completion:nil];
????????????????????????????}?//回调?[self?callCompletionBlockForOperation:strongOperation?completion:completedBlock?image:transformedImage?data:downloadedData?error:nil?cacheType:SDImageCacheTypeNone?finished:finished?url:url];
????????????????????????});
????????????????????}?else?{?//下载成功且未自定义代理--默认保存?if?(downloadedImage?&&?finished)?{
????????????????????????????[self.imageCache?storeImage:downloadedImage?imageData:downloadedData?forKey:key?toDisk:cacheOnDisk?completion:nil];
????????????????????????}
????????????????????????[self?callCompletionBlockForOperation:strongOperation?completion:completedBlock?image:downloadedImage?data:downloadedData?error:nil?cacheType:SDImageCacheTypeNone?finished:finished?url:url];
????????????????????}
????????????????}?if?(finished)?{
????????????????????[self?safelyRemoveOperationFromRunning:strongOperation];
????????????????}
????????????}];?@synchronized(operation)?{
????????????????operation.cancelBlock?=?^{
????????????????????[self.imageDownloader?cancel:subOperationToken];
????????????????????__strong?__typeof(weakOperation)?strongOperation?=?weakOperation;
????????????????????[self?safelyRemoveOperationFromRunning:strongOperation];
????????????????};
????????????}
????????}?else?if?(cachedImage)?{?//本地有图片--回调、关闭当前操作?__strong?__typeof(weakOperation)?strongOperation?=?weakOperation;
????????????[self?callCompletionBlockForOperation:strongOperation?completion:completedBlock?image:cachedImage?data:cachedData?error:nil?cacheType:cacheType?finished:YES?url:url];
????????????[self?safelyRemoveOperationFromRunning:operation];
????????}?else?{?//本地没有、也不下载--回调、关闭当前操作?__strong?__typeof(weakOperation)?strongOperation?=?weakOperation;
????????????[self?callCompletionBlockForOperation:strongOperation?completion:completedBlock?image:nil?data:nil?error:nil?cacheType:SDImageCacheTypeNone?finished:YES?url:url];
????????????[self?safelyRemoveOperationFromRunning:operation];
????????}
????}];?return?operation;
}
SDWebImageManager流程图
  • 业务层:

缓存&&磁盘操作(SDImageCache)

-?(nullable?NSOperation?*)queryCacheOperationForKey:(nullable?NSString?*)key?done:(nullable?SDCacheQueryCompletedBlock)doneBlock?{?if?(!key)?{?if?(doneBlock)?{
????????????doneBlock(nil,?nil,?SDImageCacheTypeNone);
????????}?return?nil;
????}?//?First?check?the?in-memory?cache...?//搜索磁盘缓存?UIImage?*image?=?[self?imageFromMemoryCacheForKey:key];?if?(image)?{?NSData?*diskData?=?nil;?if?(image.images)?{
????????????diskData?=?[self?diskImageDataBySearchingAllPathsForKey:key];
????????}?if?(doneBlock)?{
????????????doneBlock(image,?diskData,?SDImageCacheTypeMemory);
????????}?return?nil;
????}?NSOperation?*operation?=?[NSOperation?new];?dispatch_async(self.ioQueue,?^{?if?(operation.isCancelled)?{?//?do?not?call?the?completion?if?cancelled?return;
????????}?@autoreleasepool?{?//搜索硬盘?NSData?*diskData?=?[self?diskImageDataBySearchingAllPathsForKey:key];?UIImage?*diskImage?=?[self?diskImageForKey:key];?//缓存到内存、默认为YES?if?(diskImage?&&?self.config.shouldCacheImagesInMemory)?{?NSUInteger?cost?=?SDCacheCostForImage(diskImage);?//使用NSChache缓存。?[self.memCache?setObject:diskImage?forKey:key?cost:cost];
????????????}?if?(doneBlock)?{?dispatch_async(dispatch_get_main_queue(),?^{
????????????????????doneBlock(diskImage,?diskData,?SDImageCacheTypeDisk);
????????????????});
????????????}
????????}
????});?return?operation;
}?//查询缓存?-?(nullable?UIImage?*)imageFromMemoryCacheForKey:(nullable?NSString?*)key?{?//self.memCache??为NSCache实例?return?[self.memCache?objectForKey:key];
}?//查询磁盘?-?(nullable?UIImage?*)diskImageForKey:(nullable?NSString?*)key?{?NSData?*data?=?[self?diskImageDataBySearchingAllPathsForKey:key];?if?(data)?{?//图片解码、调整方向?UIImage?*image?=?[[SDWebImageCodersManager?sharedInstance]?decodedImageWithData:data];?//调整图片缩放比例?@2x/@3x?image?=?[self?scaledImageForKey:key?image:image];?//压缩图片?if?(self.config.shouldDecompressImages)?{
????????????image?=?[[SDWebImageCodersManager?sharedInstance]?decompressedImageWithImage:image?data:&data?options:@{SDWebImageCoderScaleDownLargeImagesKey:?@(NO)}];
????????}?return?image;
????}?else?{?return?nil;
????}
}?//写入缓存?&&?磁盘?-?(void)storeImage:(nullable?UIImage?*)image
?????????imageData:(nullable?NSData?*)imageData
????????????forKey:(nullable?NSString?*)key
????????????toDisk:(BOOL)toDisk
????????completion:(nullable?SDWebImageNoParamsBlock)completionBlock?{?if?(!image?||?!key)?{?if?(completionBlock)?{
????????????completionBlock();
????????}?return;
????}?//?if?memory?cache?is?enabled?if?(self.config.shouldCacheImagesInMemory)?{?//写入缓存?NSUInteger?cost?=?SDCacheCostForImage(image);
????????[self.memCache?setObject:image?forKey:key?cost:cost];
????}?if?(toDisk)?{?//写入磁盘?dispatch_async(self.ioQueue,?^{?@autoreleasepool?{?NSData?*data?=?imageData;?if?(!data?&&?image)?{?//?If?we?do?not?have?any?data?to?detect?image?format,?check?whether?it?contains?alpha?channel?to?use?PNG?or?JPEG?format?SDImageFormat?format;?if?(SDCGImageRefContainsAlpha(image.CGImage))?{
????????????????????????format?=?SDImageFormatPNG;
????????????????????}?else?{
????????????????????????format?=?SDImageFormatJPEG;
????????????????????}
????????????????????data?=?[[SDWebImageCodersManager?sharedInstance]?encodedDataWithImage:image?format:format];
????????????????}
????????????????[self?storeImageDataToDisk:data?forKey:key];
????????????}?if?(completionBlock)?{?dispatch_async(dispatch_get_main_queue(),?^{
????????????????????completionBlock();
????????????????});
????????????}
????????});
????}?else?{?if?(completionBlock)?{
????????????completionBlock();
????????}
????}
}?//正式写入磁盘?-?(void)storeImageDataToDisk:(nullable?NSData?*)imageData?forKey:(nullable?NSString?*)key?{?if?(!imageData?||?!key)?{?return;
????}
?????
????[self?checkIfQueueIsIOQueue];?//如果文件中不存在磁盘缓存路径?则创建?if?(![_fileManager?fileExistsAtPath:_diskCachePath])?{
????????[_fileManager?createDirectoryAtPath:_diskCachePath?withIntermediateDirectories:YES?attributes:nil?error:NULL];
????}?//?get?cache?Path?for?image?key??得到该key的缓存路径?NSString?*cachePathForKey?=?[self?defaultCachePathForKey:key];?//?transform?to?NSUrl??将缓存路径转化为url?NSURL?*fileURL?=?[NSURL?fileURLWithPath:cachePathForKey];?//将imageData存储起来?[_fileManager?createFileAtPath:cachePathForKey?contents:imageData?attributes:nil];?//?disable?iCloud?backup??如果调用者关闭icloud?关闭iCloud备份?if?(self.config.shouldDisableiCloud)?{
????????[fileURL?setResourceValue:@YES?forKey:NSURLIsExcludedFromBackupKey?error:nil];
????}
}

由于此处只归纳正常读取下载流程的代码、所以其余关于图片过期&&释放流程的代码没有列出。后面会逐一进行归纳。


查找本地流程图

下载操作(SDWebImageDownloader)

-?(nullable?SDWebImageDownloadToken?*)downloadImageWithURL:(nullable?NSURL?*)url
???????????????????????????????????????????????????options:(SDWebImageDownloaderOptions)options
??????????????????????????????????????????????????progress:(nullable?SDWebImageDownloaderProgressBlock)progressBlock
?????????????????????????????????????????????????completed:(nullable?SDWebImageDownloaderCompletedBlock)completedBlock?{
????__weak?SDWebImageDownloader?*wself?=?self;?return?[self?addProgressCallback:progressBlock?completedBlock:completedBlock?forURL:url?createCallback:^SDWebImageDownloaderOperation?*{?//创建下载operation?__strong?__typeof?(wself)?sself?=?wself;?//超时时间?NSTimeInterval?timeoutInterval?=?sself.downloadTimeout;?if?(timeoutInterval?==?0.0)?{
????????????timeoutInterval?=?15.0;
????????}?//?In?order?to?prevent?from?potential?duplicate?caching?(NSURLCache?+?SDImageCache)?we?disable?the?cache?for?image?requests?if?told?otherwise?//创建下载策略?//SDWebImageDownloaderUseNSURLCache?则使用?NSURLRequestUseProtocolCachePolicy?缓存协议?//默认NSURLRequestReloadIgnoringLocalCacheData从原地址重新下载?NSURLRequestCachePolicy?cachePolicy?=?options?&?SDWebImageDownloaderUseNSURLCache???NSURLRequestUseProtocolCachePolicy?:?NSURLRequestReloadIgnoringLocalCacheData;?//创建下载请求?NSMutableURLRequest?*request?=?[[NSMutableURLRequest?alloc]?initWithURL:url
????????????????????????????????????????????????????????????????????cachePolicy:cachePolicy
????????????????????????????????????????????????????????????????timeoutInterval:timeoutInterval];
????????
????????request.HTTPShouldHandleCookies?=?(options?&?SDWebImageDownloaderHandleCookies);
????????request.HTTPShouldUsePipelining?=?YES;?if?(sself.headersFilter)?{
????????????request.allHTTPHeaderFields?=?sself.headersFilter(url,?[sself.HTTPHeaders?copy]);
????????}?else?{?//默认?image/*;q=0.8?request.allHTTPHeaderFields?=?sself.HTTPHeaders;
????????}?//创建下载操作?SDWebImageDownloaderOperation?*operation?=?[[sself.operationClass?alloc]?initWithRequest:request?inSession:sself.session?options:options];?//是否解压?operation.shouldDecompressImages?=?sself.shouldDecompressImages;?//证书?if?(sself.urlCredential)?{
????????????operation.credential?=?sself.urlCredential;
????????}?else?if?(sself.username?&&?sself.password)?{?//默认?账号密码为空的通用证书?operation.credential?=?[NSURLCredential?credentialWithUser:sself.username?password:sself.password?persistence:NSURLCredentialPersistenceForSession];
????????}?//优先级。默认都不是?if?(options?&?SDWebImageDownloaderHighPriority)?{
????????????operation.queuePriority?=?NSOperationQueuePriorityHigh;
????????}?else?if?(options?&?SDWebImageDownloaderLowPriority)?{
????????????operation.queuePriority?=?NSOperationQueuePriorityLow;
????????}?//向下载队列?NSOperationQueue?中?添加本次下载操作?[sself.downloadQueue?addOperation:operation];?//设置下载的顺序?是按照队列还是栈?if?(sself.executionOrder?==?SDWebImageDownloaderLIFOExecutionOrder)?{?//?Emulate?LIFO?execution?order?by?systematically?adding?new?operations?as?last?operation's?dependency?[sself.lastAddedOperation?addDependency:operation];
????????????sself.lastAddedOperation?=?operation;
????????}?return?operation;
????}];
}?//通过progressBlock&&completedBlock以及Url和SDWebImageDownloaderOperation对token进行包装?-?(nullable?SDWebImageDownloadToken?*)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock
???????????????????????????????????????????completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
???????????????????????????????????????????????????forURL:(nullable?NSURL?*)url
???????????????????????????????????????????createCallback:(SDWebImageDownloaderOperation?*(^)(void))createCallback?{?//?The?URL?will?be?used?as?the?key?to?the?callbacks?dictionary?so?it?cannot?be?nil.?If?it?is?nil?immediately?call?the?completed?block?with?no?image?or?data.?if?(url?==?nil)?{?if?(completedBlock?!=?nil)?{
????????????completedBlock(nil,?nil,?nil,?NO);
????????}?return?nil;
????}

????__block?SDWebImageDownloadToken?*token?=?nil;

????dispatch_barrier_sync(self.barrierQueue,?^{
????????SDWebImageDownloaderOperation?*operation?=?self.URLOperations[url];?if?(!operation)?{
????????????operation?=?createCallback();?//将url作为key、对应的下载操作operation作为value保存。?self.URLOperations[url]?=?operation;

????????????__weak?SDWebImageDownloaderOperation?*woperation?=?operation;
????????????operation.completionBlock?=?^{
????????????????dispatch_barrier_sync(self.barrierQueue,?^{
????????????????????SDWebImageDownloaderOperation?*soperation?=?woperation;?if?(!soperation)?return;?if?(self.URLOperations[url]?==?soperation)?{?//下载完成、移除操作?[self.URLOperations?removeObjectForKey:url];
????????????????????};
????????????????});
????????????};
????????}?//将成progressBlock以及completedBlock组装成SDCallbacksDictionary.?id?downloadOperationCancelToken?=?[operation?addHandlersForProgress:progressBlock?completed:completedBlock];?//生成下载任务标识。用于manager将来定位对应操作用?token?=?[SDWebImageDownloadToken?new];
????????token.url?=?url;
????????token.downloadOperationCancelToken?=?downloadOperationCancelToken;
????});?return?token;
}
SDWebImageDownloaderOperation是具体下载操作、设计很多网络层的东西。将来可以单独开一篇、结合AFNetWorking没准会更好。

一些启发

  • 分层的接口API设计。
#import?"UIImageView+WebCache.h"?#import?"UIButton+WebCache.h"?#import?"UIImageView+HighlightedWebCache.h"?//以及其汇总的?#import?"UIView+WebCache.h"

所有外层API与具体业务无关。
使得SDWebImageManager可以脱离View层单独运作。

  • 线程安全
@synchronized?(self.runningOperations)?{
????????[self.runningOperations?addObject:operation];
????}?if?(url)?{?@synchronized?(self.failedURLs)?{
?????????isFailedUrl?=?[self.failedURLs?containsObject:url];
?????}
}
.....

所有可能引起资源抢夺的对象操作、全部有条件锁?;?。
但是由于内嵌异常处理代码的存在、条件锁的性能是所有锁中最差的。不知道为什么SD中使用这么多。

  • 内联函数

更高效的短函数执行、替代表达式形式的宏定义。

  • 精细的缓存管理原则

详参上文提到的《磁盘清理的原则?》

  • 回调设计

SDWebImage中使用了两种、Block以及Delegate。

  • Block使用的很多、举两个例子。

======>#import?"UIView+WebCache.h"?-?(void)sd_internalSetImageWithURL:(nullable?NSURL?*)url
??????????????????placeholderImage:(nullable?UIImage?*)placeholder
???????????????????????????options:(SDWebImageOptions)options
??????????????????????operationKey:(nullable?NSString?*)operationKey
?????????????????????setImageBlock:(nullable?SDSetImageBlock)setImageBlock
??????????????????????????progress:(nullable?SDWebImageDownloaderProgressBlock)progressBlock
?????????????????????????completed:(nullable?SDExternalCompletionBlock)completedBlock
???????????????????????????context:(nullable?NSDictionary?*)context;

======>SDWebImageDownloader
-?(nullable?SDWebImageDownloadToken?*)downloadImageWithURL:(nullable?NSURL?*)url
???????????????????????????????????????????????????options:(SDWebImageDownloaderOptions)options
??????????????????????????????????????????????????progress:(nullable?SDWebImageDownloaderProgressBlock)progressBlock
?????????????????????????????????????????????????completed:(nullable?SDWebImageDownloaderCompletedBlock)completedBlock;

再来看代理

@protocol?SDWebImageManagerDelegate??@optional?/**
?*?Controls?which?image?should?be?downloaded?when?the?image?is?not?found?in?the?cache.
?*
?*?@param?imageManager?The?current?`SDWebImageManager`
?*?@param?imageURL?????The?url?of?the?image?to?be?downloaded
?*
?*?@return?Return?NO?to?prevent?the?downloading?of?the?image?on?cache?misses.?If?not?implemented,?YES?is?implied.
?*/?-?(BOOL)imageManager:(nonnull?SDWebImageManager?*)imageManager?shouldDownloadImageForURL:(nullable?NSURL?*)imageURL;
-?(nullable?UIImage?*)imageManager:(nonnull?SDWebImageManager?*)imageManager?transformDownloadedImage:(nullable?UIImage?*)image?withURL:(nullable?NSURL?*)imageURL;

不难看出、SDWebImage对回调的使用倾向于:

  • Block
    单个图片的分类、单个图片的下载。
    每个操作任务中必现的progress以及completed。
    所以、有很强的个体绑定需要或者使用次数不多时、倾向使用block

  • Delegate
    SDWebImageManager下载完成之后的自定义图片处理、是否下载某个url。
    这两个方法如果需要的话都是将会调用多次的。所以、用Delegate更好、可以将方法常驻。

  • 同理
    UITableView的使用Delegate、是用为在滚动途中、代理方法需要被不断的执行。
    UIButton也是将会被多次点击。
    UIView的动画/GCD则可以使用Block、因为只执行一次、用完释放。
    所以、在日常使用中、我们也可以参考上述原则进行设计。

  • NSMapTable

用NSMapTable代替字典来存储当前正在进行的操作、并且将value设置为NSMapTableWeakMemory。防止对应value因为强引用不能自动释放。

作者:kirito_song
链接:https://www.jianshu.com/p/3b8a7ae966d3

搜索CocoaChina微信公众号:CocoaChina
微信扫一扫
订阅每日移动开发及APP推广热点资讯
公众号:
CocoaChina
我要投稿   收藏文章
上一篇:iOS基础深入补完计划--多线程(面试题)汇总
下一篇:iOS架构模式——MV(X)的理解与实战
我来说两句
发表评论
您还没有登录!请登录注册
所有评论(0

综合评论

相关帖子

sina weixin mail 回到顶部
幸运飞艇5码公式 | 幸运飞艇开奖直播app | 北京pk | 幸运农场官网 | 重庆幸运农场预测结果 | 重庆幸运农场开奖历史 | 北京赛车pk10高手心得 | 幸运飞艇定位公式 |