用SDWebImage给图片加载加上渐现的特效

现在很多APP在tableView加载图片的时候会给图片一个加载的效果这是我做的DEMO看一下效果

图片加载.gif

首先来介绍一下这个 SDWebImage 这个著名开源框架吧, 这个开源框架的主要作用就是:

Asynchronous image downloader with cache support with an UIImageView category.

一个异步下载图片并且支持缓存的 UIImageView 分类.

就这么直译过来相信各位也能理解, 框架中最最常用的方法其实就是这个:

[self.imageView sd_setImageWithURL:[NSURL URLWithString:@"url"]
                  placeholderImage:[UIImage imageNamed:@"placeholder.png"]];

当然这个框架中还有 UIButton 的分类, 可以给 UIButton 异步加载图片, 不过这个并没有 UIImageView 分类中的这个方法常用.

这个框架的设计还是极其的优雅和简洁, 主要的功能就是这么一行代码, 而其中复杂的实现细节全部隐藏在这行代码之后, 正应了那句话:

把简洁留给别人, 把复杂留给自己.

我们已经看到了这个框架简洁的接口, 接下来我们看一下 SDWebImage 是用什么样的方式优雅地实现异步加载图片和缓存的功能呢?

接下来我们就以 UIImageView+WebCache 中的

- (void)sd_setImageWithURL:(NSURL *)url 
          placeholderImage:(UIImage *)placeholder;

这一方法为入口研究一下 SDWebImage 是怎样工作的. 我们打开上面这段方法的实现代码 UIImageView+WebCache.m

当然你也可以 git clone [email protected]:rs/SDWebImage.git 到本地来查看.

- (void)sd_setImageWithURL:(NSURL *)url 
          placeholderImage:(UIImage *)placeholder {
    [self sd_setImageWithURL:url 
            placeholderImage:placeholder 
                     options:0 
                    progress:nil 
                   completed:nil];
}

这段方法唯一的作用就是调用了另一个方法

[self sd_setImageWithURL:placeholderImage:options:progress:completed:]

在这个文件中, 你会看到很多的 sd_setImageWithURL...... 方法, 它们最终都会调用上面这个方法, 只是根据需要传入不同的参数, 这在很多的开源项目中乃至我们平时写的项目中都是很常见的. 而这个方法也是 UIImageView+WebCache 中的核心方法.

这里就不再复制出这个方法的全部实现了.

操作的管理

这是这个方法的第一行代码:

// UIImageView+WebCache
// sd_setImageWithURL:placeholderImage:options:progress:completed: #1

[self sd_cancelCurrentImageLoad];

这行看似简单的代码最开始是被我忽略的, 我后来才发现蕴藏在这行代码之后的思想, 也就是 SDWebImage 管理操作的办法.

框架中的所有操作实际上都是通过一个 operationDictionary 来管理, 而这个字典实际上是动态的添加到 UIView 上的一个属性, 至于为什么添加到 UIView 上, 主要是因为这个 operationDictionary 需要在 UIButtonUIImageView 上重用, 所以需要添加到它们的根类上.

这行代码是要保证没有当前正在进行的异步下载操作, 不会与即将进行的操作发生冲突, 它会调用:

// UIImageView+WebCache
// sd_cancelCurrentImageLoad #1

[self sd_cancelImageLoadOperationWithKey:@"UIImageViewImageLoad"]

而这个方法会使当前 UIImageView 中的所有操作都被 cancel. 不会影响之后进行的下载操作.

占位图的实现

// UIImageView+WebCache
// sd_setImageWithURL:placeholderImage:options:progress:completed: #4

if (!(options & SDWebImageDelayPlaceholder)) {
    self.image = placeholder;
}

如果传入的 options 中没有 SDWebImageDelayPlaceholder(默认情况下 options == 0), 那么就会为 UIImageView 添加一个临时的 image, 也就是占位图.


获取图片

// UIImageView+WebCache
// sd_setImageWithURL:placeholderImage:options:progress:completed: #8

if (url)

接下来会检测传入的 url 是否非空, 如果非空那么一个全局的 SDWebImageManager 就会调用以下的方法获取图片:

[SDWebImageManager.sharedManager downloadImageWithURL:options:progress:completed:]

下载完成后会调用 (SDWebImageCompletionWithFinishedBlock)completedBlockUIImageView.image 赋值, 添加上最终所需要的图片.

// UIImageView+WebCache
// sd_setImageWithURL:placeholderImage:options:progress:completed: #10

dispatch_main_sync_safe(^{
    if (!wself) return;
    if (image) {
        wself.image = image;
        [wself setNeedsLayout];
    } else {
        if ((options & SDWebImageDelayPlaceholder)) {
            wself.image = placeholder;
            [wself setNeedsLayout];
        }
    }
    if (completedBlock && finished) {
        completedBlock(image, error, cacheType, url);
    }
});

dispatch_main_sync_safe 宏定义

上述代码中的 dispatch_main_sync_safe 是一个宏定义, 点进去一看发现宏是这样定义的

#define dispatch_main_sync_safe(block)\
    if ([NSThread isMainThread]) {\
        block();\
    } else {\
        dispatch_sync(dispatch_get_main_queue(), block);\
    }

相信这个宏的名字已经讲他的作用解释的很清楚了: 因为图像的绘制只能在主线程完成, 所以, dispatch_main_sync_safe 就是为了保证 block 能在主线程中执行.


而最后, 在 [SDWebImageManager.sharedManager downloadImageWithURL:options:progress:completed:] 返回 operation同时, 也会向 operationDictionary 中添加一个键值对, 来表示操作的正在进行:

// UIImageView+WebCache
// sd_setImageWithURL:placeholderImage:options:progress:completed: #28

[self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];

它将 opertion 存储到 operationDictionary 中方便以后的 cancel.

到此为止我们已经对 SDWebImage 框架中的这一方法分析完了, 接下来我们将要分析 SDWebImageManager 中的方法

[SDWebImageManager.sharedManager downloadImageWithURL:options:progress:completed:]

SDWebImageManager

在 SDWebImageManager.h 中你可以看到关于 SDWebImageManager 的描述:

The SDWebImageManager is the class behind the UIImageView+WebCache category and likes. It ties the asynchronous downloader (SDWebImageDownloader) with the image cache store (SDImageCache). You can use this class directly to benefit from web image downloading with caching in another context than a UIView.

这个类就是隐藏在 UIImageView+WebCache 背后, 用于处理异步下载和图片缓存的类, 当然你也可以直接使用 SDWebImageManager 的上述方法 downloadImageWithURL:options:progress:completed: 来直接下载图片.

可以看到, 这个类的主要作用就是为 UIImageView+WebCacheSDWebImageDownloader, SDImageCache 之间构建一个桥梁, 使它们能够更好的协同工作, 我们在这里分析这个核心方法的源代码, 它是如何协调异步下载和图片缓存的.

// SDWebImageManager
// downloadImageWithURL:options:progress:completed: #6

if ([url isKindOfClass:NSString.class]) {
    url = [NSURL URLWithString:(NSString *)url];
}

if (![url isKindOfClass:NSURL.class]) {
    url = nil;
}SDWebImageCompletionBlock

这块代码的功能是确定 url 是否被正确传入, 如果传入参数的是 NSString 类型就会被转换为 NSURL. 如果转换失败, 那么 url 会被赋值为空, 这个下载的操作就会出错.


回归正题,想要做出这个效果,无非是调用了SDWebImage的两个方法

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock {
    [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock];
}

在completedBlock中我们完成对图片View的加载

        cell.image.alpha = 0.0;
        [UIView transitionWithView:cell.image
                          duration:1.0
                           options:UIViewAnimationOptionTransitionCrossDissolve
                        animations:^{
                            [cell.image setImage:image];
                            cell.image.alpha = 1.0;
                        } completion:NULL];
        
        }

写到这里我们可以达到了渐隐渐现的效果了,但是发现了一个问题

1.0

我们会发现图片无论是上拉还是下滑所有的图片都会重新都会再次加载那我怎样让以前加载过的图片不让他加载动画呢,所以就要用SDWebImage读取缓存的方法


SDWebImageCache

SDWebImageCache.h 这个类在源代码中有这样的注释:

SDImageCache maintains a memory cache and an optional disk cache.

它维护了一个内存缓存和一个可选的磁盘缓存, 我们先来看一下在上一阶段中没有解读的两个方法, 首先是:

- (NSOperation *)queryDiskCacheForKey:(NSString *)key 
                                 done:(SDWebImageQueryCompletedBlock)doneBlock;

这个方法的主要功能是异步的查询图片缓存. 因为图片的缓存可能在两个地方, 而该方法首先会在内存中查找是否有图片的缓存.

// SDWebImageCache
// queryDiskCacheForKey:done: #9

UIImage *image = [self imageFromMemoryCacheForKey:key];

这个 imageFromMemoryCacheForKey 方法会在 SDWebImageCache 维护的缓存 memCache 中查找是否有对应的数据, 而 memCache 就是一个 NSCache.

如果在内存中并没有找到图片的缓存的话, 就需要在磁盘中寻找了, 这个就比较麻烦了..

在这里会调用一个方法 diskImageForKey 这个方法的具体实现我在这里就不介绍了, 涉及到很多底层 Core Foundation 框架的知识, 不过这里文件名字的存储使用 MD5 处理过后的文件名.

// SDImageCache
// cachedFileNameForKey: #6

CC_MD5(str, (CC_LONG)strlen(str), r);

对于其它的实现细节也就不多说了...

如果在磁盘中查找到对应的图片, 我们会将它复制到内存中, 以便下次的使用.

// SDImageCache
// queryDiskCacheForKey:done: #24

UIImage *diskImage = [self diskImageForKey:key];
if (diskImage) {
    CGFloat cost = diskImage.size.height * diskImage.size.width * diskImage.scale;
    [self.memCache setObject:diskImage forKey:key cost:cost];
}

这些就是 SDImageCache 的核心内容了.所以我就用了判断当前是否储存过已经加载过的图片,就不让他加载的动画

- (BOOL)diskImageExistsForURL:(NSURL *)url {
    NSString *key = [self cacheKeyForURL:url];
    return [self.imageCache diskImageExistsWithKey:key];
}

最后由衷感谢draveness深入解析 iOS 开源项目

https://github.com/Draveness/iOS-Source-Code-Analyze,

最后Demo我会上传到github,欢迎互相交流和学习https://github.com/Jetsond/ZCDemo

你可能感兴趣的:(用SDWebImage给图片加载加上渐现的特效)