SDWebImage缓存图片地址一二事

最近在做一个老项目的图片下载模块优化,希望整体替换成SDWebImage。大体的逻辑分析看来,替换应该还是比较好做的,无非就是异步下载接口全部用SDWebImage,但其中有一项需求却遇到了困难:
原代码的逻辑是这样的:

  1. 异步发起下载图片任务;
  2. 图片下到后,不获取NSData或UIImage,写入本地文件;
  3. 图片下载器delegate方法出一个本地图片的filePath给发起任务对象;
  4. 发起方用这个filePath来随时使用这张图。
    先不论这种方式的效率如何,要SDWebImage来适配,首先想到的是使用SDWebImage的Cache机能,即SDWebCache。于是尝试以下代码:
[[SDWebImageManager sharedManager] downloadImageWithURL:url options:options progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
        if (image) {
            NSString *filePath = [[SDWebImageManager sharedManager].imageCache defaultCachePathForKey:[[SDWebImageManager sharedManager] cacheKeyForURL:imageURL]];
            //获得图片本地文件path
        }
    }];

结果发现地址时有时无,一时不得要领。
于是去看downloadImageWithURL的源码实现,有以下代码段:

if (transformedImage && finished) {
                                BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
                                [self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk];
                            }

                            dispatch_main_sync_safe(^{
                                if (strongOperation && !strongOperation.isCancelled) {
                                    completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);
                                }
                            });

再看storeImage的实现:

- (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];
        });
    }
}

这就比较清楚了。SDWebImage在保存图片到缓存并不是同步的,而是在一个ioQueue里异步完成。图片本地文件的读取,对SDWebImage来说应该是一种补充,而非使用图片必需存在的前提条件。对我们这种需求,可以考虑两种方案:

  1. 重建一个缓存,在SDWebImage之上,自己实现业务逻辑。
  2. 设法让SDWebImage的completeBlock在图片本地文件写完后回调。
    第一种方案等于放弃了SDWebImage的各种优势,相当不可取。第二种方式的难点在于,如何在不动SDWebImage源码的情况下,让storeImageDataToDisk执行后再调completeBlock。于是我们想到了运行时:
@implementation SDWebImageManager (DiskCacheEnsured)

- (id )downloadImageWithURL:(NSURL *)url
                                         options:(SDWebImageOptions)options
                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock
                       diskCacheEnsuredCompleted:(SDWebImageCompletionWithFinishedBlock)diskCacheEnsuredCompletedBlock
{
    return [self downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
        if (completedBlock){
            completedBlock(image, error, cacheType, finished, imageURL);
        }
        
        Ivar queueIvar = class_getInstanceVariable([SDImageCache class], "_ioQueue");
        dispatch_queue_t _ioQueue = nil;
        if (queueIvar){
            _ioQueue = object_getIvar([SDImageCache sharedImageCache], queueIvar);
        }
        //为了防止SDWebImage改了这个变量名字
        if (!_ioQueue){
            _ioQueue = dispatch_get_main_queue();
        }
        
        @weakify(image)
        @weakify(error)
        @weakify(imageURL)
        dispatch_async(_ioQueue, ^{
            dispatch_async(dispatch_get_main_queue(), ^{
                @strongify(image)
                @strongify(error)
                @strongify(imageURL)
                if (diskCacheEnsuredCompletedBlock){
                    diskCacheEnsuredCompletedBlock(image, error, cacheType, finished, imageURL);
                }
            });
        });
    }];
}
@end

这段代码的逻辑是把SDImageCache的ioQueue变量引出,先派发到这个保存文件的queue,确保保存完后再在主线程操作图片本地文件。
使用这个Category非常简单:

[[SDWebImageManager sharedManager] downloadImageWithURL:url options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
        CGFloat progress = (float)receivedSize*100/(float)expectedSize;
    } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
        if (image) {
            //获得UIImage
        }
    } diskCacheEnsuredCompleted:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL){
        if (image) {
            NSString *filePath = [self.imageCache defaultCachePathForKey:[self cacheKeyForURL:imageURL]];
            //获得图片本地文件path
        }
    }];

这样就在不侵入第三方源代码的情况下满足了需求。

你可能感兴趣的:(SDWebImage缓存图片地址一二事)