SDWebImage 源码阅读(三)
来源:互联网 发布:迦陵论诗丛稿 知乎 编辑:程序博客网 时间:2024/06/06 15:01
上一篇文章我们遗留了一些问题:
- SDImageCache : 根据 key 去缓存中查找对应的图片
- SDWebImageDownloader : 下载图片
- SDImageCache : 将图片存入缓存
前面的代码并没有特意去解析 SDImageCache 的缓存机制,这一篇我们主要带着问题来讲解 SDImageCache 类。
1. 根据 key 去缓存中查找对应的图片
SDImageCache 类中这么多函数,我们先从哪里看起呢?和 SDImageCache 相关的遗留问题有两个:根据 key 去缓存中查找对应的图片、将图片存入缓存。那我们就先从第一个遗留问题开始。
operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) { ...}];
上面的代码是 SDWebImageManager 中调用 SDImageCache 的方法,根据key去查找缓存中对应的图片,进入该方法:
- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock { if (!doneBlock) { return nil; } if (!key) { doneBlock(nil, SDImageCacheTypeNone); return nil; } // First check the in-memory cache... // 首先检查内存缓存(详情见 1.1) UIImage *image = [self imageFromMemoryCacheForKey:key]; // 如果内存中有,直接返回image if (image) { doneBlock(image, SDImageCacheTypeMemory); return nil; } // 否则,说明图片在磁盘cache中 // 采用new的方式只能采用默认的init方法完成初始化,采用alloc的方式可以用其他定制的初始化方法 NSOperation *operation = [NSOperation new]; dispatch_async(self.ioQueue, ^{ if (operation.isCancelled) { return; } @autoreleasepool { // 从硬盘缓存中取得图片(详情见 1.2) UIImage *diskImage = [self diskImageForKey:key]; // 如果磁盘中得到了该image,并且还需要缓存到内存中,为了同步最新数据 if (diskImage && self.shouldCacheImagesInMemory) { // 精确的cost应该是对象占用的字节数 NSUInteger cost = SDCacheCostForImage(diskImage); [self.memCache setObject:diskImage forKey:key cost:cost]; } dispatch_async(dispatch_get_main_queue(), ^{ doneBlock(diskImage, SDImageCacheTypeDisk); }); } }); return operation;}
1.1. 从内存缓存中获取图片
获取内存缓存中的图片
UIImage *image = [self imageFromMemoryCacheForKey:key];
点击进入 imageFromMemoryCacheForKey: 方法
- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key { return [self.memCache objectForKey:key];}
通过 key-value 从内存缓存中获取图片
1.2. 从硬盘缓存中获取图片
获取硬盘缓存中的图片
UIImage *diskImage = [self diskImageForKey:key];
点击进入 diskImageForKey: 方法
- (UIImage *)diskImageForKey:(NSString *)key { // 获取硬盘缓存的 image data(详情见 1.3) NSData *data = [self diskImageDataBySearchingAllPathsForKey:key]; if (data) { // data 转换成 image 格式(详情见 1.4) UIImage *image = [UIImage sd_imageWithData:data]; // 设置是否动图和scale大小(详情见 1.5) image = [self scaledImageForKey:key image:image]; // 判断是否要压缩图片,默认要压缩图片 if (self.shouldDecompressImages) { // 解压缩图片(详情见 1.6) image = [UIImage decodedImageWithImage:image]; } return image; } else { return nil; }}
1.3. 获取硬盘缓存的 image data
- (NSData *)diskImageDataBySearchingAllPathsForKey:(NSString *)key { // 根据图片的key获取到全路径,这个路径是MD5加密路径 // 这里嵌套了2、3层,最后的MD5加密需要注意一下 NSString *defaultPath = [self defaultCachePathForKey:key]; NSData *data = [NSData dataWithContentsOfFile:defaultPath]; if (data) { return data; } // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name // checking the key with and without the extension data = [NSData dataWithContentsOfFile:[defaultPath stringByDeletingPathExtension]]; if (data) { return data; } // 如果没有就从路径的数组中遍历所有路径取出 NSArray *customPaths = [self.customPaths copy]; for (NSString *path in customPaths) { NSString *filePath = [self cachePathForKey:key inPath:path]; NSData *imageData = [NSData dataWithContentsOfFile:filePath]; if (imageData) { return imageData; } // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name // checking the key with and without the extension imageData = [NSData dataWithContentsOfFile:[filePath stringByDeletingPathExtension]]; if (imageData) { return imageData; } } return nil;}
MD5 加密
- (NSString *)cachedFileNameForKey:(NSString *)key { const char *str = [key UTF8String]; if (str == NULL) { str = ""; } // MD5进行加密处理 unsigned char r[CC_MD5_DIGEST_LENGTH]; CC_MD5(str, (CC_LONG)strlen(str), r); NSString *filename = [NSString stringWithFormat:@"%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:@""] ? @"" : [NSString stringWithFormat:@".%@", [key pathExtension]]]; return filename;}
1.4. data 转换成 image
+ (UIImage *)sd_imageWithData:(NSData *)data { if (!data) { return nil; } UIImage *image; // 根据data的前面几个字节,判断出图片类型,是jepg、png、gif... NSString *imageContentType = [NSData sd_contentTypeForImageData:data]; // 如果是gif图片或webp图片,需要单独处理 if ([imageContentType isEqualToString:@"image/gif"]) { image = [UIImage sd_animatedGIFWithData:data]; }#ifdef SD_WEBP else if ([imageContentType isEqualToString:@"image/webp"]) { image = [UIImage sd_imageWithWebPData:data]; }#endif else { image = [[UIImage alloc] initWithData:data]; UIImageOrientation orientation = [self sd_imageOrientationFromImageData:data]; if (orientation != UIImageOrientationUp) { image = [UIImage imageWithCGImage:image.CGImage scale:image.scale orientation:orientation]; } } return image;}
1.5. 设置动图和 scale 大小
进入 scaledImageForKey: image: 方法后,又调用一个方法
- (UIImage *)scaledImageForKey:(NSString *)key image:(UIImage *)image { return SDScaledImageForKey(key, image);}
继续进入,发现这是一个 c++ 函数
inline UIImage *SDScaledImageForKey(NSString *key, UIImage *image) { if (!image) { return nil; } // 如果 image.images 存在,可以理解为 gif 图 if ([image.images count] > 0) { NSMutableArray *scaledImages = [NSMutableArray array]; // 使用递归,构建一组动图 for (UIImage *tempImage in image.images) { [scaledImages addObject:SDScaledImageForKey(key, tempImage)]; } // 根据这些 images 构成我们所需的 animated image return [UIImage animatedImageWithImages:scaledImages duration:image.duration]; } else { // 屏幕为 320x480, scale 为 1 // 屏幕为 640x960, scale 为 2 if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) { CGFloat scale = 1; // "@2x.png"的长度是7,所以此处添加了这个判断 if (key.length >= 8) { NSRange range = [key rangeOfString:@"@2x."]; if (range.location != NSNotFound) { scale = 2.0; } range = [key rangeOfString:@"@3x."]; if (range.location != NSNotFound) { scale = 3.0; } } UIImage *scaledImage = [[UIImage alloc] initWithCGImage:image.CGImage scale:scale orientation:image.imageOrientation]; image = scaledImage; } return image; }}
1.6. 解压缩图片
+ (UIImage *)decodedImageWithImage:(UIImage *)image { // while downloading huge amount of images // autorelease the bitmap context // and all vars to help system to free memory // when there are memory warning. // on iOS7, do not forget to call // [[SDImageCache sharedImageCache] clearMemory]; // 当下载大量图片,产生内存警告时 // 自动释放bitmap上下文环境和所有变量来释放系统内存空间 // 在iOS 7中不要忘记添加 // [[SDImageCache sharedImageCache] clearMemory]; if (image == nil) { // Prevent "CGBitmapContextCreateImage: invalid context 0x0" error return nil; } @autoreleasepool{ // do not decode animated images // 对于 animated images 不需要解压缩 if (image.images != nil) { return image; } CGImageRef imageRef = image.CGImage; // 图片如果有alpha通道,就返回原始image,因为jpg图片有alpha的话,就不压缩 CGImageAlphaInfo alpha = CGImageGetAlphaInfo(imageRef); BOOL anyAlpha = (alpha == kCGImageAlphaFirst || alpha == kCGImageAlphaLast || alpha == kCGImageAlphaPremultipliedFirst || alpha == kCGImageAlphaPremultipliedLast); if (anyAlpha) { return image; } // current CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(CGImageGetColorSpace(imageRef)); CGColorSpaceRef colorspaceRef = CGImageGetColorSpace(imageRef); BOOL unsupportedColorSpace = (imageColorSpaceModel == kCGColorSpaceModelUnknown || imageColorSpaceModel == kCGColorSpaceModelMonochrome || imageColorSpaceModel == kCGColorSpaceModelCMYK || imageColorSpaceModel == kCGColorSpaceModelIndexed); // 如果属于上述不支持的ColorSpace,ColorSpace就使用RGB if (unsupportedColorSpace) { colorspaceRef = CGColorSpaceCreateDeviceRGB(); } size_t width = CGImageGetWidth(imageRef); size_t height = CGImageGetHeight(imageRef); NSUInteger bytesPerPixel = 4; NSUInteger bytesPerRow = bytesPerPixel * width; NSUInteger bitsPerComponent = 8; // kCGImageAlphaNone is not supported in CGBitmapContextCreate. // Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast // to create bitmap graphics contexts without alpha info. // 当你调用这个函数的时候,Quartz创建一个位图绘制环境,也就是位图上下文。 // 当你向上下文中绘制信息时,Quartz把你要绘制的信息作为位图数据绘制到指定的内存块。 // 一个新的位图上下文的像素格式由三个参数决定: // 每个组件的位数,颜色空间,alpha选项。alpha值决定了绘制像素的透明性。 CGContextRef context = CGBitmapContextCreate(NULL, width, height, bitsPerComponent, bytesPerRow, colorspaceRef, kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast); // Draw the image into the context and retrieve the new bitmap image without alpha // 在上面创建的context绘制image,并以此获取image,而该image也将拥有alpha通道 CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context); UIImage *imageWithoutAlpha = [UIImage imageWithCGImage:imageRefWithoutAlpha scale:image.scale orientation:image.imageOrientation]; // 开始释放资源 if (unsupportedColorSpace) { CGColorSpaceRelease(colorspaceRef); } CGContextRelease(context); CGImageRelease(imageRefWithoutAlpha); return imageWithoutAlpha; }}
2. 将图片存入磁盘缓存
- (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, ^{ // 构建一个data,用来存储到disk中,默认为imageData NSData *data = imageData; // 如果image存在,但是需要重新计算(recalculate)或者data为空 // 那就要根据image重新生成新的data // 不过要是连image也为空的话,那就别存了 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 // 我们需要判断image是PNG还是JPEG // PNG的图片很容易检测出来,因为它们有一个特定的标示 (http://www.w3.org/TR/PNG-Structure.html) // PNG图片的前8个字节不许符合下面这些值(十进制表示) // 137 80 78 71 13 10 26 10 // 如果imageData为空 (举个例子,比如image在下载后需要transform,那么就imageData就会为空) // 并且image有一个alpha通道, 我们将该image看做PNG以避免透明度(alpha)的丢失(因为JPEG没有透明色) int alphaInfo = CGImageGetAlphaInfo(image.CGImage); // 该image中有透明信息,就认为image为PNG BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone || alphaInfo == kCGImageAlphaNoneSkipFirst || alphaInfo == kCGImageAlphaNoneSkipLast); BOOL imageIsPng = hasAlpha; // But if we have an image data, we will look at the preffix // 但是如果我们已经有了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 } [self storeImageDataToDisk:data forKey:key]; }); }}- (void)storeImageDataToDisk:(NSData *)imageData forKey:(NSString *)key { if (!imageData) { return; } // 首先判断disk cache的文件路径是否存在,不存在的话就创建一个 // disk cache的文件路径是存储在_diskCachePath中的 if (![_fileManager fileExistsAtPath:_diskCachePath]) { [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL]; } // get cache Path for image key // 根据image的key(一般情况下理解为image的url)组合成最终的文件路径,之前文章已经讲过了 NSString *cachePathForKey = [self defaultCachePathForKey:key]; // transform to NSUrl NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey]; // 根据存储的路径(cachePathForKey)和存储的数据(data)将其存放到iOS的文件系统 [_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil]; // disable iCloud backup // 如果不使用iCloud进行备份,就使用NSURLIsExcludedFromBackupKey if (self.shouldDisableiCloud) { [fileURL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:nil]; }}
3. 总结
查找图片和存储图片运用了好多 CGImage 相关知识,这方法不足之处还差好多,虽然基本理解了图片存储与查找,但是细节还是没有好好的研究,日后把这里作为一个重点拿出来详细的研究一下。
遗留问题:使用 SDWebImageDownloader 类下载图片
0 0
- SDWebImage 源码阅读(三)
- [iOS]SDWebImage 源码阅读(三)下载
- SDWebImage 源码阅读笔记(三)
- SDWebImage 源码阅读(一)
- SDWebImage 源码阅读(二)
- SDWebImage 源码阅读(四)
- [iOS]SDWebImage 源码阅读(一)
- [iOS]SDWebImage 源码阅读(二)缓存
- SDWebImage 源码阅读笔记(一)
- SDWebImage 源码阅读笔记(二)
- SDWebImage 源码阅读笔记(四)
- 源码阅读:SDWebImage
- 源码阅读笔记-----SDWebImage
- SDWebImage 源码阅读笔记
- SDWebImage源码阅读
- SDWebImage源代码阅读(三)
- iOS SDWebImage源码研究(三)
- 源码阅读(三)
- java的集合简介
- C语言实验——三个数排序 (sdut oj)
- 商旅集成-系统集成-接口对接-接口开发 总结
- SynchronizedMap和ConcurrentHashMap的深入分析
- kafka性能调优
- SDWebImage 源码阅读(三)
- Python分词并进行词频统计
- 记一次基于android audioservice/policy开发新功能经历
- 第二十节 类型参数(二)
- 揭秘Camera Turning
- epoch的定义 和 iteration的定义
- 安全文章
- VMWare+Win10,在Win10更新后 USB 设备使用异常
- 服务器热更新(当个文件更新)