用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 git@github.com: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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,056评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,842评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,938评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,296评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,292评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,413评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,824评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,493评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,686评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,502评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,553评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,281评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,820评论 3 305
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,873评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,109评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,699评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,257评论 2 341

推荐阅读更多精彩内容