目录
- 一、SDWebImage UML分析
- 二、SDWebImage 中 @autoreleasepool 的应用
- 三、SDWebImage 支持 GIF动图 吗?
- 四、SDWebImage 如何 区分图片格式?
- 五、SDWebImage 缓存图片的名称如何 避免重名?
- 六、SDWebImage 中 常量的定义
- 七、SDWebImage 如何保证UI操作放在主线程中执行?
- 八、SDWebImage 的 最大并发数 和 超时时长
- 九、SDWebImage 的Memory缓存和Disk缓存是用什么实现的?
- 十、SDWebImage 读取Memory和Disk的时候如何保证 线程安全?
- 十一、SDWebImage 的 Memory警告 是如何处理的!
- 十二、SDWebImage Disk缓存时长? Disk清理操作时间点? Disk清理原则?
- 十三、SDWebImage Disk目录 位于哪里?
- 十四、SDWebImage 的回调设计?
- 十五、SDWebImage 中 NS_OPTIONS 与 NS_ENUM 的使用
- 十六、SDWebImage 中的工具类介绍
- 【更新:2018-09-11】
- 【推荐:2019-06-05 iOS开发·由SDWebImage引发的知识点聚合与思考(最新呕心沥血之作)】
官方文档
SDWebImage 4.0迁移指南
Common Problems
SDWebImage/wiki
HowToUse
一、UML图和时序图
[图片上传失败...(image-d49a02-1523894672845)]
[图片上传失败...(image-e4a15e-1523894672845)]
二、SDWebImage 中@autoreleasepool的应用
- Retain count semantics in ARC
- What's @autoreleasepool
- AutoreleasePool的原理和实现
现考虑如下代码:
for (int i = 0; i < 10000; i++) {
[self doSthWith:object];
}
这段代码和笔试题关键部分大同小异。如果"doSthWith:"方法要创建一个临时对象,那么这个对象很可能会放在自动释放池里。笔试题中最后stringByAppendingString方法很有可能属于上述的方法。因此如果涉及到了自动释放池,那么问题也应该就出在上面。
注意:即便临时对象在调用完方法后就不再使用了,它们也依然处于存活状态,因为目前它们都在自动释放池里,等待系统稍后进行回收。但自动释放池却要等到该线程执行下一次事件循环时才会清空,这就意味着在执行for循环时,会有持续不断的新的临时对象被创建出来,并加入自动释放池。要等到结束for循环才会释放。在for循环中内存用量会持续上涨,而等到结束循环后,内存用量又会突然下降。
而如果把循环内的代码包裹在“自动释放池”中,那么在循环中自动释放的对象就会放在这个池,而不是在线程的主池里面。如下:
for (int i = 0; i < 1000000; i++) {
@autoreleasepool {
NSString *str = @"abc";
str = [str lowercaseString];
str = [str stringByAppendingString:@"xyz"];
}
}
新增的自动释放池可以减少内存用量,因为系统会在块的末尾把这些对象回收掉。而上述这些临时对象,正在回收之列。
自动释放池的机制就像“栈”。系统创建好池之后,将其压入栈中,而清空自动释放池相当于将池从栈中弹出。在对象上执行自动释放操作,就等于将其放入位于栈顶的那个池。
结论:@autoreleasepool利于局部变量立刻释放
三、SDWebImage 支持GIF动图吗?
3.1、SDWebImage 4.0版本之前的UIImage+GIF
类别
在SDWebImage
这个库里有一个UIImage+GIF
的类别,里面为UIImage
扩展了三个方法:
@interface UIImage (GIF)
+ (IImage *)sd_animatedGIFNamed:(NSString *)name;
+ (UIImage *)sd_animatedGIFWithData:(NSData *)data;
- (UIImage *)sd_animatedImageByScalingAndCroppingToSize:(CGSize)size;
@end
具体使用 参考文章
NSString *path = [[NSBundle mainBundle] pathForResource:@"gifTest" ofType:@"gif"];
NSData *data = [NSData dataWithContentsOfFile:path];
UIImage *image = [UIImage sd_animatedGIFWithData:data];
gifImageView.image = image;
3.2、SDWebImage 4.0版本之后的UIImage+GIF
类别
在SDWebImage
这个库里有一个UIImage+GIF
的类别,其中的扩展方法只有一个sd_animatedGIFWithData :
,它只返回数据包含的第一帧的图像
@interface UIImage (GIF)
/**
* Compatibility method - creates an animated UIImage from an NSData, it will only contain the 1st frame image
*/
+ (UIImage *)sd_animatedGIFWithData:(NSData *)data;
/**
* Checks if an UIImage instance is a GIF. Will use the `images` array
*/
- (BOOL)isGIF;
@end
具体使用
结论:SDWebImage 4.0版本之后,sd_animatedGIFWithData :
没办法实现gif加载;
3.3、SDWebImage 4.0版本之后 加载gif新方法
官方文档:
Animated Images (GIF) support
- Starting with the 4.0 version, we rely on FLAnimatedImage to take care of our animated images.
- If you use cocoapods, add
pod 'SDWebImage/GIF'
to your podfile. - To use it, simply make sure you use
FLAnimatedImageView
instead ofUIImageView
. - Note: there is a backwards compatible feature, so if you are still trying to load a GIF into a
UIImageView
, it will only show the 1st frame as a static image by default. However, you can enable the full GIF support by using the built-in GIF coder. See GIF coder - Important: FLAnimatedImage only works on the iOS platform. For macOS, use
NSImageView
withanimates
set toYES
to show the entire animated images andNO
to only show the 1st frame. For all the other platforms (tvOS, watchOS) we will fallback to the backwards compatibility feature described above
结论:
- 4.0版本之后,SD依赖 FLAnimatedImage进行了gif的加载,
- 需要我们单独导入
pod 'SDWebImage/GIF'
, - 并且需要使用
FLAnimatedImageView
代替UIImageView
- 注意事项:这里所说的就是版本兼容的问题,也就是我们之前讨论的
sd_animatedGIFWithData :
方法没办法实现gif加载了。但是,您可以使用内置的GIF编码器来启用完整的GIF支持。具体查看 GIF coder - 重要事项:
-
FLAnimatedImage
暂时仅支持iOS平台; - macOS平台使用
NSImageView
与animates
设置为YES以显示整个动画图像,而不只是显示第1帧。 - 其他平台(tvOS, watchOS),我们将退回到上面描述的向后兼容性特性。可以使用老版本的方法。
-
四、SDWebImage 如何区分图片格式?
- PNG:压缩比没有JPG高,但是无损压缩,解压缩性能高,苹果推荐的图像格式!
- JPG:压缩比最高的一种图片格式,有损压缩!最多使用的场景,照相机!解压缩的性能不好!
- GIF:序列桢动图,特点:只支持256种颜色!最流行的时候在1998~1999,有专利的!
在分类"NSData+ImageContentType.h"中
typedef NS_ENUM(NSInteger, SDImageFormat) {
SDImageFormatUndefined = -1,
SDImageFormatJPEG = 0,
SDImageFormatPNG,
SDImageFormatGIF,
SDImageFormatTIFF,
SDImageFormatWebP
};
/**
* Return image format
*
* @param data the input image data
*
* @return the image format as `SDImageFormat` (enum)
*/
+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data;
+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data {
if (!data) {
return SDImageFormatUndefined;
}
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:
// R as RIFF for WEBP
if (data.length < 12) {
return SDImageFormatUndefined;
}
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
return SDImageFormatWebP;
}
}
return SDImageFormatUndefined;
}
实现思想:将数据data转为十六进制数据,取第一个字节数据进行判断。
五、SDWebImage 缓存图片的名称如何避免重名
对『绝对路径』进行MD5
- 如果单纯使用 文件名保存,重名的几率很高!
- 使用 MD5 的散列函数!对完整的 URL 进行 md5,结果是一个 32 个字符长度的字符串!
六、SDWebImage 中常量的定义
可参考之前的文章宏(define)与常量(const)
- SD中的
FOUNDATION_EXPORT
定义与调用
// SDWebImage-umbrella.h
#ifdef __OBJC__
#import
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
// 在SDWebImageCompat.h
FOUNDATION_EXPORT NSString *const SDWebImageErrorDomain;
// 在SDWebImageCompat.m
NSString *const SDWebImageErrorDomain = @"SDWebImageErrorDomain";
- 我常用的
UIKIT_EXTERN
调用
UIKIT_EXTERN NSString * const CHECK_SUM_MQ;
NSString * const CHECK_SUM_MQ = @"123";
- 系统内部对
FOUNDATION_EXPORT
和UIKIT_EXTERN
的定义
// 在系统内部文件 UIKitDefines.h 中
#ifdef __cplusplus
#define UIKIT_EXTERN extern "C" __attribute__((visibility ("default")))
#else
#define UIKIT_EXTERN extern __attribute__((visibility ("default")))
#endif
// 系统内部文件 NSObjCRuntime.h 中
#if defined(__cplusplus)
#define FOUNDATION_EXTERN extern "C"
#else
#define FOUNDATION_EXTERN extern
#endif
#if TARGET_OS_WIN32
#if defined(NSBUILDINGFOUNDATION)
#define FOUNDATION_EXPORT FOUNDATION_EXTERN __declspec(dllexport)
#else
#define FOUNDATION_EXPORT FOUNDATION_EXTERN __declspec(dllimport)
#endif
#define FOUNDATION_IMPORT FOUNDATION_EXTERN __declspec(dllimport)
#else
#define FOUNDATION_EXPORT FOUNDATION_EXTERN
#define FOUNDATION_IMPORT FOUNDATION_EXTERN
#endif
- 结论:
-
FOUNDATION_EXTERN
在 C 中 是 extern;在C++中是 extern 'C' ;其他情况则在win32情况下; -
UIKIT_EXTERN
简单来说,就是将函数修饰为兼容以往C编译方式的、具有extern属性(文件外可见性)、public修饰的方法或变量库外仍可见的属性;
-
七、SDWebImage 如何保证UI操作放在主线程中执行?
iOS UI 操作在主线程不一定安全?
在SDWebImage的SDWebImageCompat.h中有这样一个宏定义,用来保证主线程操作,为什么要这样写?
// SDWebImageCompat.h 中
#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block)\
if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {\
block();\
} else {\
dispatch_async(dispatch_get_main_queue(), block);\
}
#endif
在此之前见到最多的是这样的:
#define dispatch_main_async_safe(block)\
if ([NSThread isMainThread]) {\
block();\
} else {\
dispatch_async(dispatch_get_main_queue(), block);\
}
对比两段代码可以发现前者有两个地方改变了,一是多了 #ifndef
,二是判断条件改变了。
显然,增加 #ifndef
是为了提高代码的严谨,防止重复定义 dispatch_main_async_safe
。
关于判断条件的改变的原因则是复杂得多了,可参考文档
GCD's Main Queue vs. Main Thread
Queues are not bound to any specific thread
分析:如何判断当前是否在main thread?
最简单的方法
检查我们当前在主线程上执行的最简单的方法是使用[NSThread isMainThread] - GCD缺少一个类似的方便的API来检查我们是否在主队列上运行,因此许多开发人员使用了NSThread API。如下:
if ([NSThread isMainThread]) {
block();
} else {
dispatch_async(dispatch_get_main_queue(), block);
}
这在大多数情况下是有效的,直到它出现了异常。下面是关于ReactiveCocoa repo问题的摘录:
ReactiveCocoa issue
潜在的问题是VektorKit API正在检查是否在主队列上调用它,而不是检查它在主线程上运行。
虽然每个应用程序都只有一个主线程,但是在这个主线程上执行许多不同的队列是可能的。
如果库(如VektorKit)依赖于在主队列上检查执行,那么从主线程上执行的非主队列调用API将导致问题。也就是说,如果在主线程执行非主队列调度的API,而这个API需要检查是否由主队列上调度,那么将会出现问题。
更安全的方法一
从技术上讲,我认为这是一个 MapKit / VektorKit
漏洞,苹果的UI框架通常保证在从主线程调用时正确工作,没有任何文档提到需要在主队列上执行代码。
但是,现在我们知道某些api不仅依赖于主线程上的运行,而且还依赖于主队列,因此检查当前队列而不是检查当前线程更安全。
检查当前队列还可以更好地利用GCD为线程提供的抽象。从技术上讲,我们不应该知道/关心主队列是一种总是绑定到主线程的特殊队列。
不幸的是,GCD没有一个非常方便的API来检查我们当前正在运行的队列(这很可能是许多开发人员首先使用NSThread.isMainThread()的原因)。
我们需要使用 dispatch_queue_set_specific
函数来将键值对与主队列相关联;稍后,我们可以使用 dispatch_queue_get_specific
来检查键和值的存在。
- (void)function {
static void *mainQueueKey = "mainQueueKey";
dispatch_queue_set_specific(dispatch_get_main_queue(), mainQueueKey, &mainQueueKey, NULL);
if (dispatch_get_specific(mainQueueKey)) {
// do something in main queue
//通过这样判断,就可以真正保证(我们在不主动搞事的情况下),任务一定是放在主队列中的
} else {
// do something in other queue
}
}
更安全的方法二 (SDWebImage使用的方法)
我们知道在使用 GCD 创建一个 queue 的时候会指定 queue_label,可以理解为队列名,就像下面:
dispatch_queue_t myQueue = dispatch_queue_create("com.apple.threadQueue", DISPATCH_QUEUE_SERIAL);
而第一个参数就是 queue_label,根据官方文档解释,这个queueLabel 是唯一的,所以SDWebImage就采用了这个方式
//取得当前队列的队列名
dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)
//取得主队列的队列名
dispatch_queue_get_label(dispatch_get_main_queue())
然后通过 strcmp 函数进行比较,如果为0 则证明当前队列就是主队列。
SDWebImage中的实例 :判断当前是否是IOQueue
- (void)checkIfQueueIsIOQueue {
const char *currentQueueLabel = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
const char *ioQueueLabel = dispatch_queue_get_label(self.ioQueue);
if (strcmp(currentQueueLabel, ioQueueLabel) != 0) {
NSLog(@"This method should be called from the ioQueue");
}
}
结论
SDWebImage 就是从判断是否在主线程执行改为判断是否由主队列上调度。而由于主队列是一个串行队列,无论任务是异步同步都不会开辟新线程,所以当前队列是主队列等价于当前在主线程上执行。可以这样说,在主队列调度的任务肯定在主线程执行,而在主线程执行的任务不一定是由主队列调度的。
八、SDWebImage 的最大并发数 和 超时时长
// SDWebImageDownloader.m -initWithSessionConfiguration:
_downloadQueue.maxConcurrentOperationCount = 6;
_downloadTimeout = 15.0;
九、SDWebImage 的Memory缓存和Disk缓存是用什么实现的?
9.1、Memory缓存实现 -- AutoPurgeCache
『AutoPurgeCache』类继承自 『NSCache』
SDWebImage
还专门实现了一个叫做 AutoPurgeCache
的类 继承自 NSCache
,相比于普通的 NSCache
, 它提供了一个在内存紧张时候释放缓存的能力。
- 自动删除机制:当系统内存紧张时,
NSCache
会自动删除一些缓存对象 - 线程安全:从不同线程中对同一个
NSCache
对象进行增删改查时,不需要加锁 - 不同于
NSMutableDictionary
、NSCache
存储对象时不会对key
进行copy
操作
@interface AutoPurgeCache : NSCache
@end
@implementation AutoPurgeCache
- (nonnull instancetype)init {
self = [super init];
if (self) {
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
return self;
}
- (void)dealloc {
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
@end
9.2、Disk缓存实现 -- NSFileManager
SDImageCache
的磁盘缓存是通过异步操作NSFileManager
存储缓存文件到沙盒来实现的。
十、读取Memory和Disk的时候如何保证线程安全?
10.1、读取Memory
NScache
是线程安全的,在多线程操作中,不需要对Cache加锁。
读取缓存的时候是在主线程进行。由于使用NSCache进行存储、所以不需要担心单个value对象的线程安全。
10.2、读取Disk
- 创建了一个名为 IO的串行队列,所有Disk操作都在此队列中,逐个执行!!
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t ioQueue;
// Create IO serial queue
_ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);
- 判断当前是否是IOQueue (原理:七、SDWebImage 如何保证UI操作放在主线程中执行?)
- (void)checkIfQueueIsIOQueue {
const char *currentQueueLabel = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
const char *ioQueueLabel = dispatch_queue_get_label(self.ioQueue);
if (strcmp(currentQueueLabel, ioQueueLabel) != 0) {
NSLog(@"This method should be called from the ioQueue");
}
}
- 在主要存储函数中,dispatch_async(self.ioQueue, ^{})
// SDImageCache.m
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
// .........
if (toDisk) {
dispatch_async(self.ioQueue, ^{
@autoreleasepool {
NSData *data = imageData;
if (!data && image) {
SDImageFormat imageFormatFromData = [NSData sd_imageFormatForImageData:data];
data = [image sd_imageDataAsFormat:imageFormatFromData];
}
[self storeImageDataToDisk:data forKey:key];
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
}
// .........
}
结论:
- 真正的磁盘缓存是在另一个IO专属线程中的一个串行队列下进行的。
- 如果你搜索self.ioQueue还能发现、不只是读取磁盘内容。
- 包括删除、写入等所有磁盘内容都是在这个IO线程进行、以保证线程安全。
- 但计算大小、获取文件总数等操作。则是在主线程进行。(看下面代码)
// SDImageCache.m
- (NSUInteger)getSize {
__block NSUInteger size = 0;
dispatch_sync(self.ioQueue, ^{
NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath];
for (NSString *fileName in fileEnumerator) {
NSString *filePath = [self.diskCachePath stringByAppendingPathComponent:fileName];
NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
size += [attrs fileSize];
}
});
return size;
}
分析:我们可以看见,不会创建新线程且切操作会顺序执行。你可能会疑惑:为什么同样都是在主线程执行,这样没有死锁。其实这个和线程没有关系,和队列有关系,只要不放在主队列就不会阻塞主队列上的操作(各种系统的UI方法),这个操作只是选择了合适的时机在主线程上跑了一下而已~
10.3、SD使用 @synchronized
- 正确使用多线程同步锁@synchronized()
- 关于 @synchronized,这儿比你想知道的还要多
@synchronized (self.failedURLs) {
isFailedUrl = [self.failedURLs containsObject:url];
}
结论:所有可能引起资源抢夺的对象操作、全部有条件锁保护。
使用@synchronized来使得代码获得原子性,从而保证多线程安全。
十一、SDWebImage 的Memory警告是如何处理的!
利用通知中心观察
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(clearMemory)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
- UIApplicationDidReceiveMemoryWarningNotification 接收到内存警告的通知
- 执行 clearMemory 方法,清理内存缓存!
十二、SDWebImage Disk缓存时长? Disk清理操作时间点? Disk清理原则?
12.1、默认为一周
// SDImageCacheConfig.m
static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week
12.2、磁盘清理时间点
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(deleteOldFiles)
name:UIApplicationWillTerminateNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(backgroundDeleteOldFiles)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
分别在『应用被杀死时』和 『应用进入后台时』进行清理操作
清理磁盘的方法
- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock;
当应用进入后台时,会涉及到『Long-Running Task』
正常程序在进入后台后、虽然可以继续执行任务。但是在时间很短内就会被挂起待机。
Long-Running可以让系统为app再多分配一些时间来处理一些耗时任务。
- (void)backgroundDeleteOldFiles {
Class UIApplicationClass = NSClassFromString(@"UIApplication");
if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
return;
}
UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)];
// 后台任务标识--注册一个后台任务
__block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
// Clean up any unfinished task business by marking where you
// stopped or ending the task outright.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
[self deleteOldFilesWithCompletionBlock:^{
//结束后台任务
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
}
12.3、磁盘清理原则
清理缓存的规则分两步进行。 第一步先清除掉过期的缓存文件。 如果清除掉过期的缓存之后,空间还不够。 那么就继续按文件时间从早到晚排序,先清除最早的缓存文件,直到剩余空间达到要求。
具体点,SDWebImage 是怎么控制哪些缓存过期,以及剩余空间多少才够呢? 通过两个属性:
@interface SDImageCacheConfig : NSObject
/**
* The maximum length of time to keep an image in the cache, in seconds
*/
@property (assign, nonatomic) NSInteger maxCacheAge;
/**
* The maximum size of the cache, in bytes.
*/
@property (assign, nonatomic) NSUInteger maxCacheSize;
maxCacheAge 和 maxCacheSize 有默认值吗?
-
maxCacheAge
在上述已经说过了,是有默认值的 1week,单位秒。 -
maxCacheSize
翻了一遍 SDWebImage 的代码,并没有对 maxCacheSize 设置默认值。 这就意味着 SDWebImage 在默认情况下不会对缓存空间设限制。可以这样设置:
[SDImageCache sharedImageCache].maxCacheSize = 1024 * 1024 * 50; // 50M
maxCacheSize 是以字节来表示的,我们上面的计算代表 50M 的最大缓存空间。 把这行代码写在你的 APP 启动的时候,这样 SDWebImage 在清理缓存的时候,就会清理多余的缓存文件了。
十三、SDWebImage Disk目录位于哪里?
- 缓存在沙盒目录下
Library/Caches
- 默认情况下,二级目录为
~/Library/Caches/default/com.hackemist.SDWebImageCache.default
- 也可自定义文件名
- (instancetype)init {
return [self initWithNamespace:@"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];
// .......
}
如何打开 真机和模拟器 沙盒文件
- 模拟器
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) objectAtIndex:0];
通过 Finder -> 前往 -> 前往文件夹 -> 将路径输入即可!
- 真机
通过 查看包内容 查看即可!
十四、SDWebImage 的回调设计?
- Block
单个图片的分类、单个图片的下载。
每个操作任务中必现的progress以及completed。
所以、有很强的个体绑定需要或者使用次数不多时、倾向使用block - Delegate
SDWebImageManager下载完成之后的自定义图片处理、是否下载某个url。
这两个方法如果需要的话都是将会调用多次的。所以、用Delegate更好、可以将方法常驻。 - 同理
UITableView的使用Delegate、是用为在滚动途中、代理方法需要被不断的执行。
UIButton也是将会被多次点击。
UIView的动画/GCD则可以使用Block、因为只执行一次、用完释放。
所以、在日常使用中、我们也可以参考上述原则进行设计。
十五、SDWebImage 中 NS_OPTIONS 与 NS_ENUM 的使用
/// SDWebImageManager.h Line 14
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
SDWebImageRetryFailed = 1 << 0, // 值为2的0次方
SDWebImageLowPriority = 1 << 1, // 值为2的1次方
SDWebImageCacheMemoryOnly = 1 << 2, // 值为2的2次方
SDWebImageProgressiveDownload = 1 << 3, // 值为2的3次方
SDWebImageRefreshCached = 1 << 4, // 值为2的4次方
SDWebImageContinueInBackground = 1 << 5, // 值为2的5次方
SDWebImageHandleCookies = 1 << 6, // 值为2的6次方
SDWebImageAllowInvalidSSLCertificates = 1 << 7, // 值为2的7次方
SDWebImageHighPriority = 1 << 8,
SDWebImageDelayPlaceholder = 1 << 9,
SDWebImageTransformAnimatedImage = 1 << 10,
SDWebImageAvoidAutoSetImage = 1 << 11,
SDWebImageScaleDownLargeImages = 1 << 12
};
/// SDImageCache.h Line 13
typedef NS_ENUM(NSInteger, SDImageCacheType) {
SDImageCacheTypeNone, // 默认从0开始
SDImageCacheTypeDisk, // 值为1
SDImageCacheTypeMemory // 值为2
};
NS_ENUM 定义 通用枚举
NS_OPTIONS 定义 位移枚举
位移枚举即是在你需要的地方可以同时存在多个枚举值如这样:
[gifImageView sd_setImageWithURL:url placeholderImage:image options:SDWebImageRefreshCached | SDWebImageRetryFailed progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
} completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
}];
而NS_ENUM定义的枚举不能几个枚举项同时存在,只能选择其中一项,像这样:
// SDImageCache.m Line 407
doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
思考
/// SDWebImageManager.m Line 157
SDWebImageDownloaderOptions downloaderOptions = 0;
if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
分析
若 options = SDWebImageLowPriority | SDWebImageCacheMemoryOnly | SDWebImageProgressiveDownload
| 运算规则:只要两个对应的二进制位有一个为1,结果位就为1,否则为0;
& 运算规则:只有两个对应的二进制位都为1时,结果位才为1,否则为0;
转换为二进制
options = 0001 | 0010 | 0100
SDWebImageLowPriority = 0001
options = 0111
SDWebImageLowPriority = 0001
if (options & SDWebImageLowPriority)
=== if(0111 & 0001)
=== if(0001)
=== if(2)
如果SDWebImageLowPriority = 1000
if (options & SDWebImageLowPriority)
=== if(0111 & 1000)
=== if(0000)
=== if(0)
十六、SDWebImage 中的工具类介绍
工具类深入研读
-
NSData+ImageContentType
: 根据图片数据获取图片的类型,比如GIF、PNG等。 -
SDWebImageCompat
: 根据屏幕的分辨倍数成倍放大或者缩小图片大小。 -
SDImageCacheConfig
: 图片缓存策略记录。比如是否解压缩、是否允许iCloud、是否允许内存缓存、缓存时间等。默认的缓存时间是一周。 -
UIImage+MultiFormat
: 获取UIImage对象对应的data、或者根据data生成指定格式的UIImage,其实就是UIImage和NSData之间的转换处理。 -
UIImage+GIF
: 对于一张图片是否GIF做判断。可以根据NSData返回一张GIF的UIImage对象,并且只返回GIF的第一张图片生成的GIF。如果要显示多张GIF,使用FLAnimatedImageView。 -
SDWebImageDecoder
: 根据图片的情况,做图片的解压缩处理。并且根据图片的情况决定如何处理解压缩。
【更新1:2018-09-11】
第三条、SDWebImage 支持 GIF动图 吗?
根据 SDWebImage 4.4.2
版本测试,得出 "UIImage+GIF.h"
中 sd_animatedGIFWithData
可以 支持 GIF动图,又可以愉快的玩耍了。
参考文档
- 天天都在用的 SDWebImage, 你了解它的缓存策略吗?
- [iOS 开发] SDWebImage 源码阅读笔记
- iOS源码补完计划--SDWebImage4.0+源码参阅(附面试题/流程图)
完结
欢迎指正补充,可联系[email protected]
原文地址:SDWebImage4-0源码探究(二)框架分析