iOS 开发每天分享优质文章面试思考

SDWebImage详细剖析其原理

2019-07-12  本文已影响0人  白水灬煮一切

图片内存层面的相当是个缓存器,以Key-Value的形式存储图片。当内存不够的时候会清除所有缓存图片。用搜索文件系统的方式做管理,文件替换方式是以时间为单位,剔除时间大于一周的图片文件。当SDWebImageManager向SDImageCache要资源时,先搜索内存层面的数据,如果有直接返回,没有的话去访问磁盘,将图片从磁盘读取出来,然后做Decoder,将图片对象放到内存层面做备份,再返回调用层。

图片加载流程简短描述:

调用接口——>占位图进行显示——>交给ImageManager管理者

ImageManager管理者——>判断URL是否为有效URL ——> 从内存中获取图片缓存,如没有——>从磁盘中获取图片缓存,如没有——>则进行下载图片——>(最大并发数6)downloadQueue 队列 添加一个图片下载的opreation任务 超时15秒( 创建NSOperation开辟线程——>自定义NSOperation子类 ,添加到字典里进行管理这些Operation对象)——>下载完图片,从字典中清除队列中移除,把图片缓存到磁盘+内存NSCache中,显示图片,内存NSCache缓存图片个数,又缓存内存大小决定。

内存警告——>清理所有内存

死亡警告+进入后天——>清理磁盘过期图片+减少图片到最大内存限制


1、入口 setImageWithURL:placeholderImage:options: 会先把 placeholderImage 显示,然后 SDWebImageManager 根据 URL 开始处理图片。

2、进入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:,交给 SDImageCache 从缓存查找图片是否已经下载 queryDiskCacheForKey:delegate:userInfo:.

3、如果内存中已经有图片缓存,SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager。

4、SDWebImageManagerDelegate 回调 webImageManager:didFinishWithImage: 到 UIImageView+WebCache 等前端展示图片。

5、如果内存缓存中没有,生成 NSInvocationOperation 添加到队列开始从硬盘查找图片是否已经缓存。

6、根据 URL的MD5摘要 对图片进行存储和读取 在硬盘缓存目录下尝试读取图片文件。这一步是在 NSOperation 进行的操作,所以回主线程进行结果回调 notifyDelegate:。

7、如果硬盘中有该图片,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存)。SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo:。进而回调展示图片。

8、如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调 imageCache:didNotFindImageForKey:userInfo:。

9、共享或重新生成一个下载器 SDWebImageDownloader 开始下载图片。以队列的方式,按照先进先出的顺序下载。这是默认的下载顺序,默认情况下,队列最大并发数是6。如果需要的话,我们可以通过SDWebImageDownloader类的maxConcurrentDownloads属性来修改。

每个下载操作的超时时间可以通过downloadTimeout属性来设置,默认值为15秒。

所有下载操作的网络响应序列化处理是放在一个自定义的并行调度队列中来处理的

下载的核心其实就是利用NSURLSession对象来加载数据。每个图片的下载都由一个Operation操作来完成,并将这些操作放到一个操作队列中。这样可以实现图片的并发下载。

10、图片下载由 NSURLSession 来做,实现相关 delegate 来判断图片下载中、下载完成和下载失败。

11、imageDownloader:didFinishWithImage: 回调给 SDWebImageManager 告知图片下载完成。

12、通知所有的 downloadDelegates 下载完成,回调给需要的地方展示图片。

13、将图片保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存。写文件到硬盘也在以单独 NSInvocationOperation 完成,避免拖慢主线程。SDImageCache 在初始化的时候会注册一些消息通知,进入后台、应用结束的时候清理过期图片。(根据磁盘中图片的路径URL中包含的访问信息找到每一个最后访问时间,跟此刻之前一周时间进行比较,系统带有的遍历方法逐个超过一周删除,没有超过就跳过——>设置的最大内存的一半,超过就进行删除,先按访问时间倒序排序,进行删除,知道内存在一半范围内停止。!

(内存缓存的处理是使用NSCache对象来实现的。NSCache是一个类似于集合的容器。它存储key-value对,这一点类似于NSDictionary类。我们通常用使用缓存来临时存储短时间使用但创建昂贵的对象。重用这些对象可以优化性能,因为它们的值不需要重新计算。另外一方面,这些对象对于程序来说不是紧要的,在内存紧张时会被丢弃。

磁盘缓存的处理则是使用NSFileManager对象来实现的。图片存储的位置是位于Cache文件夹。另外,SDImageCache还定义了一个串行队列,来异步存储图片。

iOS SDWebImage的使用和底层原理

一、SDWebImage的使用

1、SDWebImage的安装集成有2种方式

(1)直接到github地址下载,链接https://github.com/rs/SDWebImage        

(2)用cocoapods安装,在文件夹生成的podfile文件中添加pod 'SDWebImage' ,终端cd + 文件位置,然后pod install即可

2、UITableView中导入头文件UIImageView+WebCache.h

[cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder.png"]];

如果在加载完图片后,需要做些其他操作,可以使用block回调

[cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder.png"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { ... completion code here ... }]; 

3、SDWebImageManager的使用

UIImageView(WebCache) 分类的核心在于 SDWebImageManager 的下载和缓存处理,SDWebImageManager将图片下载和图片缓存组合起来了。SDWebImageManager也可以单独使用。

SDWebImageManager *manager = [SDWebImageManager sharedManager];

[manager loadImageWithURL:imageURL

                      options:0

                    progress:^(NSInteger receivedSize, NSInteger expectedSize) {

                        // progression tracking code

                    }

                    completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {

                        if (image) {

                            // do something with image

                        }

                    }];

4、单独使用SDWebImageDownloader异步下载图片

我们还可以单独使用 SDWebImageDownloader 来下载图片,但是图片内容不会缓存。

SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader];

[downloader downloadImageWithURL:imageURL

                            options:0

                            progress:^(NSInteger receivedSize, NSInteger expectedSize) {

                                // progression tracking code

                            }

                          completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {

                              if (image && finished) {

                                  // do something with image

                              }

                          }];

5、单独使用SDImageCache异步缓存图片

SDImageCache 支持内存缓存和异步的磁盘缓存(可选),如果你想单独使用 SDImageCache 来缓存数据的话,可以使用单例,也可以创建一个有独立命名空间的 SDImageCache 实例。

添加缓存的方法:

[[SDImageCache sharedImageCache] storeImage:myImage forKey:myCacheKey];

默认情况下,图片数据会同时缓存到内存和磁盘中,如果你想只要内存缓存的话,可以使用下面的方法:

[[SDImageCache sharedImageCache] storeImage:myImage forKey:myCacheKey toDisk:NO];

读取缓存时可以使用 queryDiskCacheForKey:done: 方法,图片缓存的 key 是唯一的,通常就是图片的 absolute URL。

SDImageCache *imageCache = [[SDImageCache alloc] initWithNamespace:@"myNamespace"];

[imageCache queryDiskCacheForKey:myCacheKey done:^(UIImage *image) {

        // image is not nil if image was found

    }];

6、自定义缓存key

有时候,一张图片的 URL 中的一部分可能是动态变化的(比如获取权限上的限制),所以我们只需要把 URL 中不变的部分作为缓存用的 key。

SDWebImageManager.sharedManager.cacheKeyFilter = ^(NSURL *url) {

            url = [[NSURL alloc] initWithScheme:url.scheme host:url.host path:url.path];

            return [url absoluteString];

        };

二、使用过程中常见问题

问题 1:使用 UITableViewCell 中的 imageView 加载不同尺寸的网络图片时会出现尺寸缩放问题。

解决方案: 

自定义 UITableViewCell,重写 -layoutSubviews 方法,调整位置尺寸; 

或者直接弃用 UITableViewCell 的 imageView,自己添加一个 imageView 作为子控件。

问题 2:图片刷新问题:SDWebImage 在进行缓存时忽略了所有服务器返回的 caching control 设置,并且在缓存时没有做时间限制,这也就意味着图片 URL 必须是静态的了,要求服务器上一个 URL 对应的图片内容不允许更新。但是如果存储图片的服务器不由自己控制,也就是说 图片内容更新了,URL 却没有更新,这种情况怎么办?

解决方案:在调用 sd_setImageWithURL: placeholderImage: options:方法时设置 options 参数为 SDWebImageRefreshCached,这样虽然会降低性能,但是下载图片时会照顾到服务器返回的 caching control。

问题 3:在加载图片时,如何添加默认的 progress indicator ? 

解决方案:在调用 -sd_setImageWithURL:方法之前,先调用下面的方法:

[imageView sd_setShowActivityIndicatorView:YES];

[imageView sd_setIndicatorStyle:UIActivityIndicatorViewStyleGray];

问题4:如果在加载图片的过程中出现程序报错(App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.)

你需要操作如下--------

(1)、在Info.plist中添加 NSAppTransportSecurity 类型 Dictionary ;

(2)、在 NSAppTransportSecurity 下添加 NSAllowsArbitraryLoads 类型Boolean ,值设为 YES;

三、SDWebImage底层原理

流程图

1)当我门需要获取网络图片的时候,我们首先需要的便是URL,获得URL后我们SDWebImage实现的并不是直接去请求网路,而是检查图片缓存中有没有和URl相关的图片,如果有则直接返回image,如果没有则进行下一步。

2)当图片缓存中没有图片时,SDWebImage依旧不会直从网络上获取,而是检查沙盒中是否存在图片,如果存在,则把沙盒中对应的图片存进image缓存中,然后按着第一步的判断进行。

3)如果沙盒中也不存在,则显示占位图,然后根据图片的下载队列缓存判断是否正在下载,如果下载则等待,避免二次下载。如果不存则创建下载队列,下载完毕后将下载操作从队列中清除,并且将image存入图片缓存中。

4)刷新UI(当然根据实际情况操作)将image存入沙盒缓存。

四、SDWebImage源码实现步骤

常见的四种加载方式

1、无占位图直接加载(如果缓存中存在改图片则直接获取无需重新下载增加磁盘缓存)

- (void)sd_setImageWithURL:(nullable NSURL *)url {

    [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil];

}

2、有占位图直接加载(如果URL加载不到则展示占位图,如果缓存中存在改图片则直接获取无需重新下载增加磁盘缓存)

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

    [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];

}

3、有占位图直接加载,并且实现图片加载完之后的Block可以继续完成下一步操作(如果URL加载不到则展示占位图,如果缓存中存在改图片则直接获取无需重新下载增加磁盘缓存)

- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock {

    [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock];

}

4、可以选择options的形式加载图片,(如果URL加载不到则展示占位图,如果缓存中存在改图片则直接获取无需重新下载增加磁盘缓存)

- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options {

    [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil];

}

/*使用可更换optionsType的加载方式

                    -------------Options 枚举下的加载方式-----------

SDWebImageRetryFailed 默认情况下,当URL无法下载时,URL就会被列入黑名单,这样库就不会继续尝试了。此标记禁用此黑名单。

SDWebImageLowPriority  默认情况下,图像下载是在UI交互过程中启动的,这标志禁用该特性,导致在UIScrollView减速方面延迟下载。

SDWebImageCacheMemoryOnly  此标记禁用磁盘缓存

SDWebImageProgressiveDownload 此标志可以进行渐进式下载,在下载过程中,图像会逐步显示,就像浏览器所做的那样。默认情况下,图像只显示一次完全下载。

SDWebImageRefreshCached  即使缓存了映像,也要尊重HTTP响应缓存控制,并在需要的情况下从远程位置刷新映像。磁盘缓存将由NSURLCache来处理,而不是使用SDWebImage,这会导致轻微的性能下降。这个选项有助于处理在同一个请求URL后面更改的图像,例如Facebook图形api概要图。如果刷新了缓存的图像,那么完成块就会被缓存的图像和最后的图像再次调用一次。只有当你不能用嵌入的缓存破坏参数使你的url静态时,才使用这个标志。

SDWebImageContinueInBackground  在iOS 4+中,如果应用程序进入后台,可以继续下载图片。这是通过请求系统在后台获得额外的时间来完成请求完成的。如果后台任务过期,操作将被取消。

SDWebImageHandleCookies 通过设置NSMutableURLRequest来处理存储在NSHTTPCookieStore中的cookie。HTTPShouldHandleCookies =是的;

SDWebImageAllowInvalidSSLCertificates  启用不受信任的SSL证书。用于测试目的。在生产中使用谨慎。

SDWebImageHighPriority 默认情况下,图像按顺序装载在队列中。这个标志把它们移到队列的前面。

SDWebImageDelayPlaceholder  默认情况下,在图像加载时加载占位符图像。此标志将延迟加载占位符图像,直到图像完成加载。

SDWebImageTransformAnimatedImage  我们通常不会在动画图像上调用transformdownloade昏暗委托方法,因为大多数转换代码会把它搞砸。无论如何,使用这个标志来转换它们。* /

SDWebImageAvoidAutoSetImage  默认情况下,图像会在下载后添加到imageView中。但是在某些情况下,我们想要在设置图像之前有手(例如,应用一个过滤器或将它添加到交叉衰减动画中)使用这个标记如果你想在成功完成时手工设置图像

SDWebImageScaleDownLargeImages  默认情况下,图像会被解码,以尊重它们原来的大小。在iOS上,这一标志将把图像缩小到与设备受限内存兼容的大小。*如果“SDWebImageProgressiveDownload”标志设置禁用缩减。

以上四个常用方法,点击进去查看内部实现代码时,你会发现所有方法都指向------>

最终执行方法

源码注释解释的含义是

用url、占位符和自定义选项设置imageView图像。下载是异步的和缓存的。

@param url是图像的url。

@param占位符将首先设置的图像,直到图像请求完成。

@param选择在下载图像时使用的选项。

@参见SDWebImageOptions用于可能的值。

@param progressBlock在下载@note时,在后台队列

@param completedBlock的后台进程中执行进程块,该块是在操作完成时被调用的。这个块没有返回值,并将所请求的UIImage作为第一个参数。在出现错误时,图像参数为nil,第二个参数可能包含一个NSError。第三个参数是一个布尔值,指示是否从本地缓存或网络检索图像。第四个参数是原始图像url。

下面是图解(上面展示了每句话的备注)

1、设置展位图,并且取消当前下载任务

图1

2、创建一个新的下载操作

图2

3、下载操作代码(判断流是否存在,如果不存在则将其存在失效列表中,防止重复下载无效流)-----在这里他对NSString和NSURL的转换做了判断。原因是(非常常见的错误是使用NSString对象而不是NSURL发送URL。出于某种奇怪的原因,Xcode不会对这种类型的不匹配发出任何警告。在这里,我们通过允许url作为NSString传递来确保这个错误。)括号当中是文档给出的解释,所以这里做了强制转换。

图3

4、利用唯一生成的key,到缓存--->内存---->磁盘中分别寻找。

图4

5、寻找的顺序 缓存---->磁盘---->在没有就下载

图5

下载流程之后就是清理缓存(种类) 1、清理所有内存缓存镜像 2、清理所有磁盘缓存镜像3、清理过期的缓存映像从磁盘中删除

/*

    异步清除所有磁盘缓存映像。非阻塞方法-立即返回。@param完成一个应该在缓存过期后执行的块(可选)

    注意:这里要注意[[SDImageCache sharedImageCache] clearDisk];方法会报错,下面clearDiskOnCompletion的方法会替代上面的方法

    */

    [[SDImageCache sharedImageCache] clearDiskOnCompletion:^{

    }];

    /*

    Clear all memory cached images --->清除所有缓存镜像

    */

    [[SDImageCache sharedImageCache] clearMemory];

    /*

    异步将所有过期的缓存映像从磁盘中删除。非阻塞方法-立即返回。@param completionBlock在缓存过期后执行(可选)--->故名思义他是不能删除你当前缓存的大小的

    */

[self.diskCache removeExpiredData];//清理过期图片&&磁盘内存清理

//APP退到后台时——>开辟一个后台任务——>进行过期图片的清理、磁盘内存清理

- (void)applicationDidEnterBackground:(NSNotification *)notification{}

根据磁盘中图片的路径URL中包含的访问信息找到每一个最后访问时间,跟此刻之前一周时间进行比较,系统带有的遍历方法逐个超过一周删除,没有超过就跳过——>设置的最大内存的一半,超过就进行删除,先按访问时间倒序排序,进行删除,知道内存在一半范围内停止。!)

五、总结

SDWebImage加载图片的流程:

1. 入口 setImageWithURL:placeholderImage:options: 会先把 placeholderImage显示,然后 SDWebImageManager 根据 URL 开始处理图片。

2. 进入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:,交给 SDImageCache 从缓存查找图片是否已经下载 queryDiskCacheForKey:delegate:userInfo:.

3. 先从内存图片缓存查找是否有图片,如果内存中已经有图片缓存,SDImageCacheDelegate回调 imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager。

4. SDWebImageManagerDelegate 回调 webImageManager:didFinishWithImage: 到 UIImageView+WebCache等前端展示图片。

5. 如果内存缓存中没有,生成 NSInvocationOperation添加到队列开始从硬盘查找图片是否已经缓存。

6. 根据 URLKey在硬盘缓存目录下尝试读取图片文件。这一步是在 NSOperation 进行的操作,所以回主线程进行结果回调 notifyDelegate:。

7. 如果上一操作从硬盘读取到了图片,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存)。SDImageCacheDelegate回调 imageCache:didFindImage:forKey:userInfo:。进而回调展示图片。

8. 如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调 imageCache:didNotFindImageForKey:userInfo:。

9. 共享或重新生成一个下载器 SDWebImageDownloader 开始下载图片。

10. 图片下载由 NSURLSession来做,实现相关 delegate 来判断图片下载中、下载完成和下载失败。

11. connection:didReceiveData: 中利用 ImageIO做了按图片下载进度加载效果。

12. connectionDidFinishLoading: 数据下载完成后交给 SDWebImageDecoder 做图片解码处理。

13. 图片解码处理在一个 NSOperationQueue完成,不会拖慢主线程 UI。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多。

14. 在主线程 notifyDelegateOnMainThreadWithInfo: 宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo: 回调给 SDWebImageDownloader。

15. imageDownloader:didFinishWithImage: 回调给 SDWebImageManager告知图片下载完成。

16. 通知所有的 downloadDelegates下载完成,回调给需要的地方展示图片。

17. 将图片保存到 SDImageCache中,内存缓存和硬盘缓存同时保存。写文件到硬盘也在以单独 NSInvocationOperation 完成,避免拖慢主线程。

18. SDImageCache 在初始化的时候会注册一些消息通知,在内存警告退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片

19. SDWI 也提供了 UIButton+WebCache 和 MKAnnotationView+WebCache,方便使用。

20. SDWebImagePrefetcher 可以预先下载图片,方便后续使用。

上一篇下一篇

猜你喜欢

热点阅读