原文出处:Yasin的简书 http://www.jianshu.com/p/b1045c3fc8d0git
图片在APP中占有重要的角色,对图片作好缓存是重要的一项工做。
[TOC]github
不喜欢理论的能够直接跳到下面的Demo实践部分算法
缓存按照保存位置能够分为两类:内存缓存、硬盘缓存(FMDB、CoreData...)。
咱们常说的数据缓存
包含内存缓存、硬盘缓存和网络请求URL缓存。其中网络请求URL缓存也包含内存缓存和硬盘缓存。数据库
网络请求除了客户端须要作简单的配置外,最主要须要服务器支持,服务端也很简单,只须要在response里面设置Cache-Control字段就好了.缓存
最多见的URL缓存实现方式:NSURLCache
。NSURLCache能够在memory 和 disk 上缓存。
AFNetWorking是基于NSURLSession
(iOS7以上的网络请求框架),在生成配置的时候有三种配置选择服务器
1 + (NSURLSessionConfiguration *)defaultSessionConfiguration; 2 //默认会话模式(default):工做模式相似于原来的NSURLConnection,使用的是基于磁盘缓存的持久化策略,使用用户keychain中保存的证书进行认证受权。 3 + (NSURLSessionConfiguration *)ephemeralSessionConfiguration; 4 //瞬时会话模式(ephemeral):该模式不使用磁盘保存任何数据。全部和会话相关的caches,证书,cookies等都被保存在RAM中,所以当程序使会话无效,这些缓存的数据就会被自动清空。 5 + (NSURLSessionConfiguration *)backgroundSessionConfiguration:(NSString *)identifier; 6 //后台会话模式(background):该模式在后台完成上传和下载,在建立Configuration对象的时候须要提供一个NSString类型的ID用于标识完成工做的后台会话。
也就是说default同时实现了内存缓存和硬盘缓存,ephemeral实现了内存缓存,对于图片下载咱们固然选择default。
咱们还能够对缓存的大小进行设置,只须要对NSURLCache进行初始化就能够了cookie
在-application:didFinishLaunchingWithOptions:
中对[NSURLCache sharedURLCache]
进行初始化设置:网络
1 NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024 2 diskCapacity:20 * 1024 * 1024 3 diskPath:nil]; 4 [NSURLCache setSharedURLCache:URLCache];
也能够单独对NSURLSession
的configuration
进行设置,
在AFNetWorking中对于图片网络请求设置了20M的内存缓存和150M的硬盘缓存:session
1 + (NSURLCache *)defaultURLCache { 2 return [[NSURLCache alloc] initWithMemoryCapacity:20 * 1024 * 1024 3 diskCapacity:150 * 1024 * 1024 4 diskPath:@"com.alamofire.imagedownloader"]; 5 }
缓存策略是指对网络请求缓存若是处理,是使用缓存仍是不使用app
1 NSURLRequestUseProtocolCachePolicy: 对特定的 URL 请求使用网络协议中实现的缓存逻辑。这是默认的策略。 2 NSURLRequestReloadIgnoringLocalCacheData:数据须要从原始地址加载。不使用现有缓存。 3 NSURLRequestReloadIgnoringLocalAndRemoteCacheData:不只忽略本地缓存, 4 同时也忽略代理服务器或其余中间介质目前已有的、协议容许的缓存。 5 NSURLRequestReturnCacheDataElseLoad:不管缓存是否过时,先使用本地缓存数据。 6 若是缓存中没有请求所对应的数据,那么从原始地址加载数据。 7 NSURLRequestReturnCacheDataDontLoad:不管缓存是否过时,先使用本地缓存数据。 8 若是缓存中没有请求所对应的数据,那么放弃从原始地址加载数据, 9 请求视为失败(即:“离线”模式)。 10 NSURLRequestReloadRevalidatingCacheData:从原始地址确认缓存数据的合法性后, 11 缓存数据就可使用,不然从原始地址加载。
在AFNetWorking中一样对configuration
进行设置
1 configuration.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
若是你继承AFImageDownloader从新实现了他的初始化,requestCachePolicy
注意AFImageDownloader中只有三种才设置了缓存
1 case NSURLRequestUseProtocolCachePolicy: 2 case NSURLRequestReturnCacheDataElseLoad: 3 case NSURLRequestReturnCacheDataDontLoad:
AFNetWorking3.0放弃了NSCache做为图片内存缓存管理,这让我很是不解。
有人说它的性能和 key 的类似度有关,若是有大量类似的 key (好比 "1", "2", "3", ...),NSCache 的存取性能会降低得很是厉害,大量的时间被消耗在 CFStringEqual() 上,不知这是否是放弃使用NSCache的缘由。
像素在内存中的布局和它在磁盘中的存储方式并不相同。考虑一种简单的状况:每一个像素有R、G、B和alpha四个值,每一个值占用1字节,所以每一个像素占用4字节的内存空间。一张1920*1080的照片(iPhone6 Plus的分辨率)一共有2,073,600个像素,所以占用了超过8Mb的内存。可是一张一样分辨率的PNG格式或JPEG格式的图片通常状况下不会有这么大。这是由于JPEG将像素数据进行了一种很是复杂且可逆的转化。
AFNetWorking3.0的图片缓存类貌似是基于这个理论来作内存大小管理的(以前AF的内存大小计算方法有错,我修改了一下提交了,如今已经审核经过合并进去了,哈哈哈哈哈,我也算是贡献过AF了)。AFNetWorking2.x中仍是使用AFImageCache
进行memory上缓存。
NSCache
在memory上缓存,相似于NSMutableDictionary
,以 哈希算法 管理。有自动清理机制,当缓存到memory时,若是memory空间不够,则会自动删除memory中当前界面不使用的空间。
AFAutoPurgingImageCache使用NSMutableDictionary <NSString* , AFCachedImage*>进行内存缓存映射,并进行管理,当内存警告时就清空NSMutableDictionary。若是内存占用超过限制,则按照时间顺序进行删除。
就是咱们常说的把数据保存在本地,好比FMDB、CoreData、归档、NSUserDefaults、NSFileManager等等,这里就很少说了。
AFNetWorking3.0没有直接作图片硬盘缓存,而是经过URL缓存作的硬盘缓存。也就是说,若是内存缓存没有读取到图片,就会调用下载逻辑,经过下载缓存的内存缓存硬盘缓存来获取到已下载过的图片,若是没有下载过,就会从新下载。
若是咱们本身作图片硬盘缓存建议使用NSFileManager,由于通常图片data会比较大,测试证实路径缓存会比放在数据库有更高的性能。
1 NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; //使用default配置,自带网络请求缓存 2 [config setHTTPAdditionalHeaders:@{@"Accept":@"image/*"}];//设置网络数据格式 3 NSURLSession *session = [NSURLSession sessionWithConfiguration:config]; 4 NSURLRequest *request = [NSURLRequest requestWithURL:url]; 5 WEAKSELF 6 NSURLSessionDataTask *task = [session dataTaskWithRequest:request 7 completionHandler:^(NSData * _Nullable data, 8 NSURLResponse * _Nullable response, NSError * _Nullable error) { 9 //使用’获取数据(NSURLSessionDataTask)‘的方式发起请求 10 UIImage *image = [UIImage imageWithData:data]; 11 dispatch_async(dispatch_get_main_queue(), ^{ 12 weakSelf.imageView.image = image; 13 }); 14 }]; 15 [task resume];
咱们经过连续两次下载图片能够发现defaultSessionConfiguration下NSURLSession会自动作网络请求缓存。
导入头文件#import "UIImageView+AFNetworking.h"
使用特别简单,只有一行代码:[imageView setImageWithURL:url];
UIImageView+AFNetworking作了内存缓存和基于NSURLSession的网络请求缓存,并无作硬盘缓存,估计是考虑到图片的网络请求硬盘缓存足以知足须要,因此省略了额外的硬盘缓存,内存缓存加快了读取速度,这个是很是有必要的。
进入UIImageView+AFNetworking代码分析:
1 if ([urlRequest URL] == nil) { 2 [self cancelImageDownloadTask]; 3 self.image = placeholderImage; 4 return; 5 } 6 //若是新传入的URL为空则取消图片下载并设置图片为默认图
1 if ([self isActiveTaskURLEqualToURLRequest:urlRequest]){ 2 return; 3 } 4 //若是新传入的URL与当前URL相同则直接返回,不然取消当前下载,从新进行图片查找下载
UIImage *cachedImage = [imageCache imageforRequest:urlRequest withAdditionalIdentifier:nil]; //从内存缓存中读取image,若是没有则发起新的请求
1 AFImageDownloader *downloader = [[self class] sharedImageDownloader]; 2 //使用单例下载,内存缓存为downloader.imageCache 3 //downloader设置的网络请求20M的内存缓存和150M的硬盘缓存 4 //downloader设置的网络请求缓存策略为NSURLRequestUseProtocolCachePolicy 5 //imageCache设置了内存60M最大100M 6 //网络请求发起前会再次判断imageCache中是否含有该image
使用Charles查看图片下载的网络请求发生了几回,判断缓存是否成功。
其中硬盘缓存须要写入时间,网络请求完成后略等一下,不然硬盘缓存不会生效
若是没有对NSURLRequest
的URLCache
进行设置,默认是使用[NSURLCache sharedURLCache]
,因此若是有须要能够以下设置
1 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 2 // Override point for customization after application launch. 3 [AFNetworkActivityIndicatorManager sharedManager].enabled = YES; 4 //网络请求时状态栏网络状态小转轮 5 6 NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024 7 diskCapacity:20 * 1024 * 1024 8 diskPath:nil]; 9 //内存4M,硬盘20M 10 [NSURLCache setSharedURLCache:URLCache]; 11 12 return YES; 13 }