缓存部分从这部分开始
operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {
我们进到里面看看SDWebImage的缓存是怎么做的。
if (!doneBlock) {
return nil;
}
if (!key) {
doneBlock(nil, SDImageCacheTypeNone);
return nil;
}
首先是一些参数的判断,必须都要存在。
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
doneBlock(image, SDImageCacheTypeMemory);
return nil;
}
这几句代码是先从内存中根据key(url)取图片。
- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key {
return [self.memCache objectForKey:key];
}
再看memCache的类型
@property (strong, nonatomic) NSCache *memCache;
会发现它内存缓存用的是NSCache,可能一般做一些缓存大家就直接用字典了,其实这样是不对的,首先没有NSCache那么便于管理,清理内存也不方便。
另外,可能大家没有注意到,memCache并不是普通的NSCache,而是作者继承NSCache而实用的AutoPurgeCache.
为什么要这么做呢?
http://stackoverflow.com/questions/19546054/nscache-crashing-when-memory-limit-is-reached-only-on-ios-7/19549090#19549090
看一下这个就知道了,在iOS7以后NSCache不能在内存警告的时候自动清除内存,经过我的测试确实是这样的,所以作者在这里继承了一个NScache,在里面加入了这样一个通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
只要一收到内存警告的通知就清空内存,这样就不会因为内存问题而崩溃了。
好,下面讲如果从内存中没有取到图片然后做什么。
NSOperation *operation = [NSOperation new];
dispatch_async(self.ioQueue, ^{
if (operation.isCancelled) {
return;
}
@autoreleasepool {
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, SDImageCacheTypeDisk);
});
}
});
首先看这个ioQueue,他在创建的时候是一个串行队列,这样首先就是线程安全的了,然后由于io过程比较耗时,放在线程中也不会造成阻塞。
再看@autoreleasepool{};
他能够保证在区域结束时变量能够马上被释放,防止从硬盘将图片取出之后造成内存过高。
下面看从硬盘取出图片的过程。
- (UIImage *)diskImageForKey:(NSString *)key {
NSData *data = [self diskImageDataBySearchingAllPathsForKey:key];
if (data) {
UIImage *image = [UIImage sd_imageWithData:data];
image = [self scaledImageForKey:key image:image];
if (self.shouldDecompressImages) {
image = [UIImage decodedImageWithImage:image];
}
return image;
}
else {
return nil;
}
}
首先取出图片的NSData数据,然后根据图片的格式做相应的操作,比如gif动图,会取出图片数组,然后用
[images addObject:[UIImage imageWithCGImage:image scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp]];
来返回可以动的图片。除此之外,还有webp类型喝普通类型。
然后还会根据图片的方向做一定的翻转。并且对图片进行解压(主要是去掉Alpha通道,不然系统会自动做这个操作,影响效率)。
然后会计算出最后图片的大小并放入内存中。
以上是取图片的过程,下面看存图片的过程。
从网络取回图片后,会掉用下面的方法,之前还有一个代理方法,可以不实现。
- (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk {
if (!image || !key) {
return;
}
// if memory cache is enabled
if (self.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(image);
[self.memCache setObject:image forKey:key cost:cost];
}
if (toDisk) {
dispatch_async(self.ioQueue, ^{
NSData *data = imageData;
if (image && (recalculate || !data)) {
#if TARGET_OS_IPHONE
// We need to determine if the image is a PNG or a JPEG
// PNGs are easier to detect because they have a unique signature (http://www.w3.org/TR/PNG-Structure.html)
// The first eight bytes of a PNG file always contain the following (decimal) values:
// 137 80 78 71 13 10 26 10
// If the imageData is nil (i.e. if trying to save a UIImage directly or the image was transformed on download)
// and the image has an alpha channel, we will consider it PNG to avoid losing the transparency
int alphaInfo = CGImageGetAlphaInfo(image.CGImage);
BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
alphaInfo == kCGImageAlphaNoneSkipFirst ||
alphaInfo == kCGImageAlphaNoneSkipLast);
BOOL imageIsPng = hasAlpha;
// But if we have an image data, we will look at the preffix
if ([imageData length] >= [kPNGSignatureData length]) {
imageIsPng = ImageDataHasPNGPreffix(imageData);
}
if (imageIsPng) {
data = UIImagePNGRepresentation(image);
}
else {
data = UIImageJPEGRepresentation(image, (CGFloat)1.0);
}
#else
data = [NSBitmapImageRep representationOfImageRepsInArray:image.representations usingType: NSJPEGFileType properties:nil];
#endif
}
[self storeImageDataToDisk:data forKey:key];
});
}
}
首先判断是否需要放入内存,如果需要,就放到内存中。
然后判断是否需要放到硬盘中,如果需要:
又把这个过程放入异步串行队列中,并且根据图片的类型把图片变成二进制数据存入硬盘中。
后有有一个地方,可以禁止icloud同步图片。
// disable iCloud backup
if (self.shouldDisableiCloud) {
[fileURL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:nil];
}