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
原创粉丝点击