基于AFNetWorking3.0的图片缓存分析

来自: http://ios.jobbole.com/84084/

图片在APP中占有重要的角色,对图片做好缓存是重要的一项工作。[TOC]

理论

不喜欢理论的可以直接跳到下面的Demo实践部分

缓存介绍

缓存按照保存位置可以分为两类:内存缓存、硬盘缓存(FMDB、CoreData…)。

我们常说的 网络请求缓存 包含内存缓存、硬盘缓存和URL缓存。

图片缓存思路

图片缓存流程图.png

URL缓存

网络请求除了客户端需要做简单的配置外,最主要需要服务器支持,服务端也很简单,只需要在response里面设置Cache-Control字段就行了.

最常见的URL缓存实现方式: NSURLCache 。NSURLCache可以在memory 和 disk 上缓存。

AFNetWorking是基于 NSURLSession (iOS7以上的网络请求框架),在生成配置的时候有三种配置选择

+ (NSURLSessionConfiguration *)defaultSessionConfiguration;    //默认会话模式(default):工作模式类似于原来的NSURLConnection,使用的是基于磁盘缓存的持久化策略,使用用户keychain中保存的证书进行认证授权。  + (NSURLSessionConfiguration *)ephemeralSessionConfiguration;    //瞬时会话模式(ephemeral):该模式不使用磁盘保存任何数据。所有和会话相关的caches,证书,cookies等都被保存在RAM中,因此当程序使会话无效,这些缓存的数据就会被自动清空。  + (NSURLSessionConfiguration *)backgroundSessionConfiguration:(NSString *)identifier;    //后台会话模式(background):该模式在后台完成上传和下载,在创建Configuration对象的时候需要提供一个NSString类型的ID用于标识完成工作的后台会话。
+ (NSURLSessionConfiguration *)defaultSessionConfiguration;    //默认会话模式(default):工作模式类似于原来的NSURLConnection,使用的是基于磁盘缓存的持久化策略,使用用户keychain中保存的证书进行认证授权。  + (NSURLSessionConfiguration *)ephemeralSessionConfiguration;    //瞬时会话模式(ephemeral):该模式不使用磁盘保存任何数据。所有和会话相关的caches,证书,cookies等都被保存在RAM中,因此当程序使会话无效,这些缓存的数据就会被自动清空。  + (NSURLSessionConfiguration *)backgroundSessionConfiguration:(NSString *)identifier;    //后台会话模式(background):该模式在后台完成上传和下载,在创建Configuration对象的时候需要提供一个NSString类型的ID用于标识完成工作的后台会话。

也就是说default同时实现了内存缓存和硬盘缓存,ephemeral实现了内存缓存,对于图片下载我们当然选择default。我们还可以对缓存的大小进行设置,只需要对NSURLCache进行初始化就可以了

实现初始化

在 -application:didFinishLaunchingWithOptions: 中对 [NSURLCache sharedURLCache] 进行初始化设置:

NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024                                                           diskCapacity:20 * 1024 * 1024                                                               diskPath:nil];  [NSURLCache setSharedURLCache:URLCache];
NSURLCache *URLCache = [[NSURLCachealloc] initWithMemoryCapacity:4 * 1024 * 1024                                                          diskCapacity:20 * 1024 * 1024                                                              diskPath:nil];  [NSURLCachesetSharedURLCache:URLCache];

也可以单独对 NSURLSession 的 configuration 进行设置,

在AFNetWorking中对于图片网络请求设置了20M的内存缓存和150M的硬盘缓存:

+ (NSURLCache *)defaultURLCache {      return [[NSURLCache alloc] initWithMemoryCapacity:20 * 1024 * 1024                                           diskCapacity:150 * 1024 * 1024                                               diskPath:@"com.alamofire.imagedownloader"];  }
+ (NSURLCache *)defaultURLCache {      return [[NSURLCachealloc] initWithMemoryCapacity:20 * 1024 * 1024                                          diskCapacity:150 * 1024 * 1024                                              diskPath:@"com.alamofire.imagedownloader"];  }

缓存策略

缓存策略是指对网络请求缓存如果处理,是使用缓存还是不使用

NSURLRequestUseProtocolCachePolicy: 对特定的 URL 请求使用网络协议中实现的缓存逻辑。这是默认的策略。  NSURLRequestReloadIgnoringLocalCacheData:数据需要从原始地址加载。不使用现有缓存。  NSURLRequestReloadIgnoringLocalAndRemoteCacheData:不仅忽略本地缓存,        同时也忽略代理服务器或其他中间介质目前已有的、协议允许的缓存。  NSURLRequestReturnCacheDataElseLoad:无论缓存是否过期,先使用本地缓存数据。        如果缓存中没有请求所对应的数据,那么从原始地址加载数据。  NSURLRequestReturnCacheDataDontLoad:无论缓存是否过期,先使用本地缓存数据。        如果缓存中没有请求所对应的数据,那么放弃从原始地址加载数据,        请求视为失败(即:“离线”模式)。  NSURLRequestReloadRevalidatingCacheData:从原始地址确认缓存数据的合法性后,        缓存数据就可以使用,否则从原始地址加载。
NSURLRequestUseProtocolCachePolicy: 对特定的 URL 请求使用网络协议中实现的缓存逻辑。这是默认的策略。  NSURLRequestReloadIgnoringLocalCacheData:数据需要从原始地址加载。不使用现有缓存。  NSURLRequestReloadIgnoringLocalAndRemoteCacheData:不仅忽略本地缓存,        同时也忽略代理服务器或其他中间介质目前已有的、协议允许的缓存。  NSURLRequestReturnCacheDataElseLoad:无论缓存是否过期,先使用本地缓存数据。        如果缓存中没有请求所对应的数据,那么从原始地址加载数据。  NSURLRequestReturnCacheDataDontLoad:无论缓存是否过期,先使用本地缓存数据。        如果缓存中没有请求所对应的数据,那么放弃从原始地址加载数据,        请求视为失败(即:“离线”模式)。  NSURLRequestReloadRevalidatingCacheData:从原始地址确认缓存数据的合法性后,        缓存数据就可以使用,否则从原始地址加载。

在AFNetWorking中同样对 configuration 进行设置

configuration.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
configuration.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;

如果你继承AFImageDownloader重新实现了他的初始化, requestCachePolicy 注意AFImageDownloader中只有三种才设置了缓存

case NSURLRequestUseProtocolCachePolicy:  case NSURLRequestReturnCacheDataElseLoad:  case NSURLRequestReturnCacheDataDontLoad:
case NSURLRequestUseProtocolCachePolicy:  case NSURLRequestReturnCacheDataElseLoad:  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 进行内存缓存映射,并进行管理,当内存警告时就清空NSMutableDictionary。如果内存占用超过限制,则按照时间顺序进行删除。

硬盘缓存

就是我们常说的把数据保存在本地,比如FMDB,CoreData,归档,NSUserDefaults,NSFileManager等等,这里就不多说了。图片缓存建议使用NSFileManager,因为一般图片data会比较大,测试证明路径缓存会比放在数据库有更高的性能。

实践

Demo下载

使用NSURLSession做网络请求缓存。

NSURLSessionConfiguration config = [NSURLSessionConfiguration defaultSessionConfiguration]; //使用default配置,自带网络请求缓存 [config setHTTPAdditionalHeaders:@{@"Accept":@"image/ "}];//设置网络数据格式 NSURLSession session = [NSURLSession sessionWithConfiguration:config]; NSURLRequest request = [NSURLRequest requestWithURL:url]; WEAKSELF NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * Nullable data, NSURLResponse * Nullable response, NSError * Nullable error) { //使用’获取数据(NSURLSessionDataTask)‘的方式发起请求 UIImage *image = [UIImage imageWithData:data]; dispatch async(dispatch get main_queue(), ^{ weakSelf.imageView.image = image; }); }]; [task resume];

    NSURLSessionConfiguration *config = [NSURLSessionConfigurationdefaultSessionConfiguration];    //使用default配置,自带网络请求缓存      [configsetHTTPAdditionalHeaders:@{@"Accept":@"image/*"}];//设置网络数据格式      NSURLSession *session = [NSURLSessionsessionWithConfiguration:config];      NSURLRequest *request = [NSURLRequestrequestWithURL:url];      WEAKSELF      NSURLSessionDataTask *task = [sessiondataTaskWithRequest:requestcompletionHandler:^(NSData * _Nullabledata, NSURLResponse * _Nullableresponse, NSError * _Nullableerror) { //使用’获取数据(NSURLSessionDataTask)‘的方式发起请求          UIImage *image = [UIImageimageWithData:data];          dispatch_async(dispatch_get_main_queue(), ^{              weakSelf.imageView.image = image;          });      }];      [taskresume];
</div>

使用AFNetWorking下载图片

导入头文件 #import "UIImageView+AFNetworking.h"
使用: [imageView setImageWithURL:url];
UIImageView+AFNetworking做了内存缓存,和基于NSURLSession的网络请求缓存

代码分析:

if ([urlRequest URL] == nil) {          [self cancelImageDownloadTask];          self.image = placeholderImage;          return;      }  //如果新传入的URL为空则取消图片下载并设置图片为默认图
if ([urlRequestURL] == nil) {          [self cancelImageDownloadTask];          self.image = placeholderImage;          return;      }  //如果新传入的URL为空则取消图片下载并设置图片为默认图
if ([self isActiveTaskURLEqualToURLRequest:urlRequest]){          return;      }  //如果新传入的URL与当前URL相同则直接返回,否则取消当前下载,重新进行图片查找下载
if ([self isActiveTaskURLEqualToURLRequest:urlRequest]){          return;      }  //如果新传入的URL与当前URL相同则直接返回,否则取消当前下载,重新进行图片查找下载
UIImage *cachedImage = [imageCache imageforRequest:urlRequest withAdditionalIdentifier:nil];  //从内存缓存中读取image,如果没有则发起新的请求
UIImage *cachedImage = [imageCacheimageforRequest:urlRequestwithAdditionalIdentifier:nil];  //从内存缓存中读取image,如果没有则发起新的请求
AFImageDownloader *downloader = [[self class] sharedImageDownloader];  //使用单例下载,内存缓存为downloader.imageCache  //downloader设置的网络请求20M的内存缓存和150M的硬盘缓存  //downloader设置的网络请求缓存策略为NSURLRequestUseProtocolCachePolicy  //imageCache设置了内存60M最大100M  //网络请求发起前会再次判断imageCache中是否含有该image
AFImageDownloader *downloader = [[self class] sharedImageDownloader];  //使用单例下载,内存缓存为downloader.imageCache  //downloader设置的网络请求20M的内存缓存和150M的硬盘缓存  //downloader设置的网络请求缓存策略为NSURLRequestUseProtocolCachePolicy  //imageCache设置了内存60M最大100M  //网络请求发起前会再次判断imageCache中是否含有该image

测试

使用Charles查看图片下载的网络请求发生了几次,判断缓存是否成功。其中硬盘缓存需要写入时间,网络请求完成后略等一下,否则硬盘缓存不会生效

设置默认网络缓存大小

如果没有对 NSURLRequest 的 URLCache 进行设置,默认是使用 [NSURLCache sharedURLCache] ,所以如果有需要可以如下设置

  • (BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(NSDictionary )launchOptions { // Override point for customization after application launch. [AFNetworkActivityIndicatorManager sharedManager].enabled = YES; //网络请求时状态栏网络状态小转轮

    NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024 diskCapacity:20 * 1024 * 1024 diskPath:nil]; //内存4M,硬盘20M [NSURLCache setSharedURLCache:URLCache];

    return YES; }

- (BOOL)application:(UIApplication *)applicationdidFinishLaunchingWithOptions:(NSDictionary *)launchOptions {      // Override point for customization after application launch.      [AFNetworkActivityIndicatorManagersharedManager].enabled = YES;      //网络请求时状态栏网络状态小转轮         NSURLCache *URLCache = [[NSURLCachealloc] initWithMemoryCapacity:4 * 1024 * 1024                                                          diskCapacity:20 * 1024 * 1024                                                              diskPath:nil];      //内存4M,硬盘20M      [NSURLCachesetSharedURLCache:URLCache];         return YES;  }
</div> </div>