【原】SDWebImage源码阅读(五)

本文转载请注明出处 —— polobymulberry-博客园

1. 前言


前面的代码并没有特意去讲SDWebImage的缓存机制,主要是想单独开一章节专门讲解缓存。之前我们也遇到一些缓存的属性和方法,比如storeImage、queryDiskCacheForKey、memCache等等。

SDWebImage的缓存分为两个部分,一个内存缓存,使用NSCache实现,另一个就是硬盘缓存(disk),使用NSFileManager实现。

不过这么多函数,我们先从哪看起呢?就从给我印象最深的queryDiskCacheForKey看起。主要是因为这个函数返回的是一个NSOperation。和SDWebImageManager关系紧密,尤其是和SDWebImageCombinedOperation的cacheOperation,直接就是作为其返回值。

2. queryDiskCacheForKey


之前简单的介绍了一下queryDiskCacheForKey函数实现。具体的细节并没有介绍。尤其是对queryDiskCacheForKey中的done block中有关cache的部分没有细说。这里doneBlock先不讨论,先讨论queryDiskCacheForKey中的cache部分。

最先看到的关于cache的部分:

// 首先根据key(一般指的是图片的url)去内存缓存获取image
UIImage *image = [self imageFromMemoryCacheForKey:key];

这个imageFromMemoryCacheForKey的具体实现:

// 简单的封装了NSCache的objectForKey方法
- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key {
return [self.memCache objectForKey:key];
}

这里,很自然就会想到,objectForKey的对应方法是setObject:forKey:。所以我们搜一下看看SDWebImage在哪里使用了setObject:forKey:

总共有三处地方:

  1. queryDiskCacheForKey:done:
  2. imageFromDiskCacheForKey:key
  3. storeImage:recalculateFromImage:imageData:forKey:toDisk

2.1 queryDiskCacheForKey:done:

// 获取到disk上缓存的image
UIImage *diskImage = [self diskImageForKey:key];
// 如果diskImage存在,并且需要使用memory cache
// 就将diskImage缓存到memory cache中
if (diskImage && self.shouldCacheImagesInMemory) {
// cost 被用来计算缓存中所有对象的代价。当内存受限或者所有缓存对象的总代价超过了最大允许的值时,缓存会移除其中的一些对象。
// 通常,精确的 cost 应该是对象占用的字节数。
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}

这里只有SDCacheCostForImage函数需要细看一下,该函数本质就是计算diskImage所要占用的字节数:

// C语言函数
// FOUNDATION_STATIC_INLINE表示static __inline__,属于runtime范畴
FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
// 这里我觉得这样写不是很好,如果这样写就更直观了
// return (height * scale) * (width * scale)
return image.size.height * image.size.width * image.scale * image.scale;
}

2.2 imageFromDiskCacheForKey:key

用途和上面一样,就是从disk中获取的image,还需更新到内存缓存中。

2.3 storeImage:recalculateFromImage:imageData:forKey:toDisk

其实也是和上面一样,都是为了更新到内存缓存中:

// 如果可以使用内存缓存
if (self.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(image);
[self.memCache setObject:image forKey:key cost:cost];
}

这里比较关键的其实是storeImage这个函数的使用场景:

  • 在SDWebImageManager中的downloadImageWithURL中,成功下载到图片downloadedImage后(或者进行了transform)使用该函数对image进行缓存

我们来具体分析下这个函数。函数中的前两个if语句比较好理解,也解释过了。主要集中在if(toDisk)这个语句中,而toDisk为YES表示应该是要往disk memory中存储。

整个语句块是放在一个ioQueue的dispatch_queue_t中的:

dispatch_async(self.ioQueue, ^{
// ......
});

这个ioQueue,我们从字面上理解,就是一个磁盘io的dispatch_queue_t。说简单点,就是每个下载来的图片,需要进行磁盘io的过程都放在ioQueue中执行。

剩下的部分主要做了两件事:

  • 1.根据imageData和image生成待存储的data
  • 2.利用NSFileManager将待存储的data存储起来
// 构建一个data,用来存储到disk中,默认值为imageData
NSData *data = imageData; // 如果image存在,但是需要重新计算(recalculate)或者data为空
// 那就要根据image重新生成新的data
// 不过要是连image也为空的话,那就别存了
if (image && (recalculate || !data)) {
#if TARGET_OS_IPHONE
// 我们需要判断image是PNG还是JPEG
// PNG的图片很容易检测出来,因为它们有一个特定的标示 (http://www.w3.org/TR/PNG-Structure.html)
// PNG图片的前8个字节不许符合下面这些值(十进制表示)
// 137 80 78 71 13 10 26 10 // 如果imageData为空l (举个例子,比如image在下载后需要transform,那么就imageData就会为空)
// 并且image有一个alpha通道, 我们将该image看做PNG以避免透明度(alpha)的丢失(因为JPEG没有透明色)
int alphaInfo = CGImageGetAlphaInfo(image.CGImage); // 获取image中的透明信息
// 该image中确实有透明信息,就认为image为PNG
BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
alphaInfo == kCGImageAlphaNoneSkipFirst ||
alphaInfo == kCGImageAlphaNoneSkipLast);
BOOL imageIsPng = hasAlpha; // 但是如果我们已经有了imageData,我们就可以直接根据data中前几个字节判断是不是PNG
if ([imageData length] >= [kPNGSignatureData length]) {
// ImageDataHasPNGPreffix就是为了判断imageData前8个字节是不是符合PNG标志
imageIsPng = ImageDataHasPNGPreffix(imageData);
}
// 如果image是PNG格式,就是用UIImagePNGRepresentation将其转化为NSData,否则按照JPEG格式转化,并且压缩质量为1,即无压缩
if (imageIsPng) {
data = UIImagePNGRepresentation(image);
}
else {
data = UIImageJPEGRepresentation(image, (CGFloat)1.0);
}
#else
// 当然,如果不是在iPhone平台上,就使用下面这个方法。不过不在我们研究范围之内
data = [NSBitmapImageRep representationOfImageRepsInArray:image.representations usingType: NSJPEGFileType properties:nil];
#endif
} // 获取到需要存储的data后,下面就要用fileManager进行存储了
if (data) {
// 首先判断disk cache的文件路径是否存在,不存在的话就创建一个
// disk cache的文件路径是存储在_diskCachePath中的
if (![_fileManager fileExistsAtPath:_diskCachePath]) {
[_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
} // 根据image的key(一般情况下理解为image的url)组合成最终的文件路径
// 上面那个生成的文件路径只是一个文件目录,就跟/cache/images/img1.png和cache/images/的区别一样
// defaultCachePathForKey后面会详解
NSString *cachePathForKey = [self defaultCachePathForKey:key];
// 这个url可不是网络端的url,而是file在系统路径下的url
// 比如/foo/bar/baz --------> file:///foo/bar/baz
NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
// 根据存储的路径(cachePathForKey)和存储的数据(data)将其存放到iOS的文件系统
[_fileManager createFileAtPath:cachePathForKey contents:data attributes:nil]; // 如果不使用iCloud进行备份,就使用NSURLIsExcludedFromBackupKey
if (self.shouldDisableiCloud) {
[fileURL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:nil];
}
}

2.3.1 defaultCachePathForKey

// 简单封装了cachePathForKey:inPath
- (NSString *)defaultCachePathForKey:(NSString *)key {
return [self cachePathForKey:key inPath:self.diskCachePath];
} // cachePathForKey:inPath
- (NSString *)cachePathForKey:(NSString *)key inPath:(NSString *)path {
// 根据传入的key创建最终要存储时的文件名
NSString *filename = [self cachedFileNameForKey:key];
// 将存储的文件路径和文件名绑定在一起,作为最终的存储路径
return [path stringByAppendingPathComponent:filename];
} // cachedFileNameForKey:
- (NSString *)cachedFileNameForKey:(NSString *)key {
const char *str = [key UTF8String];
if (str == NULL) {
str = "";
}
// 使用了MD5进行加密处理
// 开辟一个16字节(128位:md5加密出来就是128bit)的空间
unsigned char r[CC_MD5_DIGEST_LENGTH];
// 官方封装好的加密方法
// 把str字符串转换成了32位的16进制数列(这个过程不可逆转) 存储到了r这个空间中
CC_MD5(str, (CC_LONG)strlen(str), r);
// 最终生成的文件名就是 "md5码"+".文件类型"
NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
r[], r[], r[], r[], r[], r[], r[], r[], r[], r[], r[],
r[], r[], r[], r[], r[], [[key pathExtension] isEqualToString:@""] ? @"" : [NSString stringWithFormat:@".%@", [key pathExtension]]]; return filename;
}

2.4 小结

上面的几个分析基本上已经把内存缓存的存储和磁盘缓存的存储讲了一下。

  • 内存缓存的存储主要就是使用NSCache自带的setObject:forKey:及其衍生方法
  • 硬盘缓存的存储主要是使用NSFileManager进行存储

已经讲完存储了,那么就不得不提及clear缓存。

3. clear缓存


我们简单看一下clear的方式,发现以下几个函数需要注意:

  1. removeImageForKeyfromDisk:withCompletion: // 异步地将image从缓存(内存缓存以及可选的磁盘缓存)中移除
  2. clearMemory // 清楚内存缓存上的所有image
  3. clearDisk // 清除磁盘缓存上的所有image
  4. cleanDisk // 清除磁盘缓存上过期的image

3.1 removeImageForKeyfromDisk:withCompletion:

这个函数其实是removeImageForKey:等一系列函数的基础,类似sd_setImageWithURL:placeholderImage:options:progress:completed:函数。

该函数主要是根据key来删除对应缓存image:

- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion {

    if (key == nil) {
return;
} // shouldCacheImagesInMemory为YES表示该图片会缓存到了内存
// 既然缓存到了内存,就要先将内存缓存中的image移除
// 使用的是NSCache的removeObjectForKey:
if (self.shouldCacheImagesInMemory) {
[self.memCache removeObjectForKey:key];
} // 如果要删除磁盘缓存中的image
if (fromDisk) {
// 有关io的部分,都要放在ioQueue中
dispatch_async(self.ioQueue, ^{
// 磁盘缓存移除使用的是NSFileManager的removeItemAtPath:error
[_fileManager removeItemAtPath:[self defaultCachePathForKey:key] error:nil];
// 如果用户实现了completion了,就在主线程调用completion()
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}
});
} else if (completion){ // 如果用户实现了completion了,就在主线程调用completion()
completion();
} }

3.2 clearMemory

简单地调用NSCache的removeAllObjects。

3.3 clearDisk

封装了clearDiskOnCompletion:函数:

- (void)clearDisk {
[self clearDiskOnCompletion:nil];
}

clearDiskOnCompletion:

- (void)clearDiskOnCompletion:(SDWebImageNoParamsBlock)completion
{
dispatch_async(self.ioQueue, ^{
// 先将存储在diskCachePath中缓存全部移除,然后新建一个空的diskCachePath
[_fileManager removeItemAtPath:self.diskCachePath error:nil];
[_fileManager createDirectoryAtPath:self.diskCachePath
withIntermediateDirectories:YES
attributes:nil
error:NULL];
// 如果实现了completion,就在主线程中调用
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}
});
}

3.4 cleanDisk

简单封装了cleanDiskWithCompletionBlock:

- (void)cleanDisk {
[self cleanDiskWithCompletionBlock:nil];
}

cleanDiskWithCompletionBlock:

// 实现了一个简单的缓存清除策略:清除修改时间最早的file
- (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock {
dispatch_async(self.ioQueue, ^{
// 这两个变量主要是为了下面生成NSDirectoryEnumerator准备的
// 一个是记录遍历的文件目录,一个是记录遍历需要预先获取文件的哪些属性
NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey]; // 递归地遍历diskCachePath这个文件夹中的所有目录,此处不是直接使用diskCachePath,而是使用其生成的NSURL
// 此处使用includingPropertiesForKeys:resourceKeys,这样每个file的resourceKeys对应的属性也会在遍历时预先获取到
// NSDirectoryEnumerationSkipsHiddenFiles表示不遍历隐藏文件
NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
includingPropertiesForKeys:resourceKeys
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:NULL];
// 获取文件的过期时间,SDWebImage中默认是一个星期
// 不过这里虽然称*expirationDate为过期时间,但是实质上并不是这样。
// 其实是这样的,比如在2015/12/12/00:00:00最后一次修改文件,对应的过期时间应该是
// 2015/12/19/00:00:00,不过现在时间是2015/12/27/00:00:00,我先将当前时间减去1个星期,得到
// 2015/12/20/00:00:00,这个时间才是我们函数中的expirationDate。
// 用这个expirationDate和最后一次修改时间modificationDate比较看谁更晚就行。
NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.maxCacheAge];
// 用来存储对应文件的一些属性,比如文件所需磁盘空间
NSMutableDictionary *cacheFiles = [NSMutableDictionary dictionary];
// 记录当前已经使用的磁盘缓存大小
NSUInteger currentCacheSize = ; // 在缓存的目录开始遍历文件. 此次遍历有两个目的:
//
// 1. 移除过期的文件
// 2. 同时存储每个文件的属性(比如该file是否是文件夹、该file所需磁盘大小,修改时间)
NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init];
for (NSURL *fileURL in fileEnumerator) {
NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:NULL]; // 当前扫描的是目录,就跳过
if ([resourceValues[NSURLIsDirectoryKey] boolValue]) {
continue;
} // 移除过期文件
// 这里判断过期的方式:对比文件的最后一次修改日期和expirationDate谁更晚,如果expirationDate更晚,就认为该文件已经过期,具体解释见上面
NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];
if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
[urlsToDelete addObject:fileURL];
continue;
} // 计算当前已经使用的cache大小,
// 并将对应file的属性存到cacheFiles中
NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
currentCacheSize += [totalAllocatedSize unsignedIntegerValue];
[cacheFiles setObject:resourceValues forKey:fileURL];
} for (NSURL *fileURL in urlsToDelete) {
// 根据需要移除文件的url来移除对应file
[_fileManager removeItemAtURL:fileURL error:nil];
} // 如果我们当前cache的大小已经超过了允许配置的缓存大小,那就删除已经缓存的文件。
// 删除策略就是,首先删除修改时间更早的缓存文件
if (self.maxCacheSize > && currentCacheSize > self.maxCacheSize) {
// 直接将当前cache大小降到允许最大的cache大小的一般
const NSUInteger desiredCacheSize = self.maxCacheSize / ; // 根据文件修改时间来给所有缓存文件排序,按照修改时间越早越在前的规则排序
NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
usingComparator:^NSComparisonResult(id obj1, id obj2) {
return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];
}]; // 每次删除file后,就计算此时的cache的大小
// 如果此时的cache大小已经降到期望的大小了,就停止删除文件了
for (NSURL *fileURL in sortedFiles) {
if ([_fileManager removeItemAtURL:fileURL error:nil]) {
// 获取该文件对应的属性
NSDictionary *resourceValues = cacheFiles[fileURL];
// 根据resourceValues获取该文件所需磁盘空间大小
NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
// 计算当前cache大小
currentCacheSize -= [totalAllocatedSize unsignedIntegerValue]; if (currentCacheSize < desiredCacheSize) {
break;
}
}
}
}
// 如果有completionBlock,就在主线程中调用
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
}

4. init方法

讲完了一些具体的缓存方法。反过来,我们研究下SDImageCache的初始化。因为之前的很多方法中的参数都是已经在init中设置好了。另外一个原因是SDImageCache使用了单例模式。所以相对来说,init方法还是很重要的。

我们先从单例模式看起,正好学习下单例模式的正确写法:

// SDImageCache使用的是单例模式
+ (SDImageCache *)sharedImageCache {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
// new = alloc + init
instance = [self new];
});
return instance;
}

4.1 initWithNamespace:

接着我们来看看init方法,原来封装了initWithNamespace:方法,并且namespace的名称为@"default"。

- (id)initWithNamespace:(NSString *)ns {
    // iOS使用的是沙盒机制,此处makeDiskCachePath就是获取Cache目录,并在Cache目录下创建default目录
    // 比如我的mac上就显示/Users/poloby/Library/Developer/CoreSimulator/Devices/4404872F-4DDD-4AEA-AAD3-71BA1931D4C1/data/Containers/Data/Application/9C7E5D14-FBF0-41F1-A533-E8ACC59FCBAC/Library/Caches/default
    // 后面详解
NSString *path = [self makeDiskCachePath:ns];
    // 最终的初始化,后面详解
return [self initWithNamespace:ns diskCacheDirectory:path];
}

4.1.1 makeDiskCachePath:

-(NSString *)makeDiskCachePath:(NSString*)fullNamespace{
// 获取当前用户应用下的Caches目录
// 返回了一个包含用户Caches目录作为第一元素的数组,所以底下用的是paths[0]
// 即/Users/poloby/Library/Developer/CoreSimulator/Devices/4404872F-4DDD-4AEA-AAD3-71BA1931D4C1/data/Containers/Data/Application/9C7E5D14-FBF0-41F1-A533-E8ACC59FCBAC/Library/Caches/
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
// 在Caches目录下构建一个fullNamespace目录,此处默认是default目录
return [paths[] stringByAppendingPathComponent:fullNamespace];
}

4.1.2 initWithNamespace:diskCacheDirectory:

(id)initWithNamespace:(NSString *)ns diskCacheDirectory:(NSString *)directory {
if ((self = [super init])) {
// 再给Caches/default/后面加上fullNamspace
// 最终可能获得的diskCachePath可能为
NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns]; // 初始化kPNGSignatureData为PNG前8字节的标志:{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}
// 用于ImageDataHasPNGPreffix这个C函数中,判断该data是不是PNG格式
kPNGSignatureData = [NSData dataWithBytes:kPNGSignatureBytes length:]; // 创建名为com.hackemist.SDWebImageCache的IO的串行队列
_ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL); // cache存储的最长时间为60 * 60 * 24 * 7,即一个星期
_maxCacheAge = kDefaultCacheMaxCacheAge; // 注意此处不是直接使用[[NSCache alloc] init]进行初始化的,而是使用了一个AutoPurgeCache
// AutoPurgeCache和NSCache不同之处在于,如果AutoPurgeCache收到一个内存警告,就会自动释放内存,调用NSCache的removeAllObjects
_memCache = [[AutoPurgeCache alloc] init];
_memCache.name = fullNamespace; // 初始化disk cache,一般情况下directory,除非你把Caches删除了
if (directory != nil) {
// 最终结果是/Users/poloby/Library/Developer/CoreSimulator/Devices/4404872F-4DDD-4AEA-AAD3-71BA1931D4C1/data/Containers/Data/Application/9C7E5D14-FBF0-41F1-A533-E8ACC59FCBAC/Library/Caches/default/com.hackemist.SDWebImageCache.default
_diskCachePath = [directory stringByAppendingPathComponent:fullNamespace];
} else {
// 如果没有找到Caches目录,或者新建default目录失败。就重新使用makeCachePath新建一个缓存目录
NSString *path = [self makeDiskCachePath:ns];
_diskCachePath = path;
} // 默认需要解压缩图片
_shouldDecompressImages = YES;
// 新建一个NSFileManager也是放在ioQueue中的
dispatch_sync(_ioQueue, ^{
_fileManager = [NSFileManager new];
}); #if TARGET_OS_IPHONE
// 订阅了app可能发生的时间
// 出现内存警告(UIApplicationDidReceiveMemoryWarningNotification),调用clearMemory
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(clearMemory)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
// 程序终止(UIApplicationWillTerminateNotification),调用cleanDisk
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(cleanDisk)
name:UIApplicationWillTerminateNotification
object:nil];
// 程序进入后台运行(UIApplicationDidEnterBackgroundNotification),调用backgroundCleanDisk
// backgroundCleanDisk就不赘述了,其实现了在后台注册了cleanDiskWithCompletionBlock函数来处理后台的磁盘缓存
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(backgroundCleanDisk)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
#endif
} return self;
}

5. 总结

SDImageCache部分有些地方处理还是很简单的,比如清除缓存策略。如果有大牛实现LRU策略就更好了。

SDWebImage源码解析到此为止,当然还有一些模块没有解析,比如MKAnnotationView+WebCache.h、UIButton+WebCache.h、UIImageView+HighlightedWebCache.h以及一些模块的某些函数也没细讲。不过相信大家举一反三的能力还是很强的。

如果大家对我的文章有什么疑问,可以留言或私信,欢迎交流。

6. 参考文章


【原】SDWebImage源码阅读(五)的更多相关文章

  1. 【原】SDWebImage源码阅读(二)

    [原]SDWebImage源码阅读(二) 本文转载请注明出处 —— polobymulberry-博客园 1. 解决上一篇遗留的坑 上一篇中对sd_setImageWithURL函数简单分析了一下,还 ...

  2. 【原】SDWebImage源码阅读(四)

    [原]SDWebImage源码阅读(四) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 SDWebImage中主要实现了NSURLConnectionDataDelega ...

  3. 【原】SDWebImage源码阅读(三)

    [原]SDWebImage源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1.SDWebImageDownloader中的downloadImageWithURL 我们 ...

  4. 【原】SDWebImage源码阅读(一)

    [原]SDWebImage源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 一直没有系统地读过整套源码,就感觉像一直看零碎的知识点,没有系统读过一本专业经典书 ...

  5. SDWebImage 源码阅读分享

    SDWebImage 源码阅读分享 疑问列表 SDWebImage 整体框架图,主要的类包含哪些 SDWebImage 如何进行缓存管理,过期失效策略,缓存更新 SDWebImage 如何多线程处理的 ...

  6. SDWebImage源码阅读-第三篇

    这一篇讲讲不常用的一些方法. 1 sd_setImageWithPreviousCachedImageWithURL: placeholderImage: options: progress: com ...

  7. SDWebImage源码阅读-第一篇

    一 题外话 之前写过一篇最新版SDWebImage的使用,也简单的介绍了一下原理.这两天正梳理自己的知识网络,觉得有必要再阅读一下源码,一是看具体实现,二是学习一下优秀开源代码的代码风格,比如接口设计 ...

  8. (原)NSQ源码阅读和分析(1)

    原文出处:https://www.cnblogs.com/lihaiping/p/12324371.html 本文记录自己在阅读和学习nsq源码的时候的一些学习笔记,主要目的是个人总结和方便后期查阅. ...

  9. SDWebImage源码阅读-第二篇

    一  SDWebImageManager的downloadImageWithURL的方法 上一篇,我们刚开了个头,分析了一下开始加载图片之前如何取消其他正在下载的任务,接着,我们回到 - (void) ...

随机推荐

  1. NodeJs之调试

    关于调试 当我们只专注于前端的时候,我们习惯性F12,这会给我们带来安全与舒心的感觉. 但是当我们使用NodeJs来开发后台的时候,我想噩梦来了. 但是也别泰国担心,NodeJs的调试是很不方便!这是 ...

  2. 从0开始搭建SQL Server AlwaysOn 第二篇(配置故障转移集群)

    从0开始搭建SQL Server AlwaysOn 第二篇(配置故障转移集群) 第一篇http://www.cnblogs.com/lyhabc/p/4678330.html第二篇http://www ...

  3. .net core快速上手

    2014年11月12日的Connect ();开发者活动上宣布将.NET堆栈基于MIT协议开源,并且提供开源保证,托管在Github上.当时的版本与最终目标相距甚远,然而有一点可以肯定的是,这是一个与 ...

  4. 如何创建Vim Dotfile?

    Dotfile是电脑系统里的隐藏文件,它是专门给更高级的用户,如开发者.程序员或工程师使用的,让他们用来调整系统.如何创建Vim-Dotfile? 可以参考以下步骤: 1. 首先,你要检查一下.vim ...

  5. WinForm 天猫2013双11自动抢红包【源码下载】

    1. 正确获取红包流程 2. 软件介绍 2.1 效果图: 2.2 功能介绍 2.2.1 账号登录 页面开始时,会载入这个网站:https://login.taobao.com/member/login ...

  6. spark处理大规模语料库统计词汇

    最近迷上了spark,写一个专门处理语料库生成词库的项目拿来练练手, github地址:https://github.com/LiuRoy/spark_splitter.代码实现参考wordmaker ...

  7. HTML文档声明

    前面的话   HTML文档通常以类型声明开始,该声明将帮助浏览器确定其尝试解析和显示的HTML文档类型.本文将详细介绍文档声明DOCTYPE 特点   文档声明必须是HTML文档的第一行.且顶格显示, ...

  8. node中的Stream-Readable和Writeable解读

    在node中,只要涉及到文件IO的场景一般都会涉及到一个类-Stream.Stream是对IO设备的抽象表示,其在JAVA中也有涉及,主要体现在四个类-InputStream.Reader.Outpu ...

  9. bzoj3208--记忆化搜索

    题目大意: 花花山峰峦起伏,峰顶常年被雪,Memphis打算帮花花山风景区的人员开发一个滑雪项目.    我们可以把风景区看作一个n*n的地图,每个点有它的初始高度,滑雪只能从高处往低处滑[严格大于] ...

  10. Web应用之LAMP源码环境部署

    一.LAMP环境的介绍 1.LAMP环境的重要性 思索许久,最终还是决定写一篇详细的LAMP的源码编译安装的实验文档,一来是为了给自己一个交代,把技术进行系统的归纳,将技术以极致的形式呈现出来,做为一 ...