专栏名称: Cocoa开发者社区
CocoaChina苹果开发中文社区官方微信,提供教程资源、app推广营销、招聘、外包及培训信息、各类沙龙交流活动以及更多开发者服务。
目录
相关文章推荐
51好读  ›  专栏  ›  Cocoa开发者社区

AFNetWorking是如何进行数据缓存的--之AFImageCache & NSURLCache 详解

Cocoa开发者社区  · 公众号  · ios  · 2016-11-02 08:08

正文

▲点击上方“CocoaChina”关注即可免费学习iOS开发


链接:https://github.com/SmithCaoCao/Translation/blob/master/README.md

英文原文:Tim Brandt《How Does Caching Work in AFNetworking? : AFImageCache & NSUrlCache Explained》


如果你是一个正在使用由Matt Thompson开发的网络库 AFNetWorking(如果你还没有使用,那你还在等什么?)的iOS开发者,也许你一直很好奇和困惑它的缓存机制,并且想要了解如何更好地充分利用它?


AFNetworking实际上利用了两套单独的缓存机制:


  • AFImagecache : 继承于NSCache,AFNetworking的图片内存缓存的类。

  • NSURLCache : NSURLConnection的默认缓存机制,用于存储NSURLResponse对象:一个默认缓存在内存,并且可以通过一些配置操作可以持久缓存到磁盘的类。


AFImageCache是如何工作的?


AFImageCache属于UIImageView+AFNetworking的一部分,继承于NSCache,以URL(从NSURLRequest对象中获取)字符串作为key值来存储UIImage对象。 AFImageCache的定义如下:(这里我们声明了一个2M内存、100M磁盘空间的NSURLCache对象。)


@interface AFImageCache : NSCache // singleton instantiation :

+ (id )sharedImageCache {

static AFImageCache *_af_defaultImageCache = nil;

static dispatch_once_t oncePredicate;

dispatch_once(&oncePredicate, ^{

_af_defaultImageCache = [[AFImageCache alloc] init];

// clears out cache on memory warning :

[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidReceiveMemoryWarningNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * __unused notification) {

[_af_defaultImageCache removeAllObjects];

}];

});

// key from [[NSURLRequest URL] absoluteString] :

static inline NSString * AFImageCacheKeyFromURLRequest(NSURLRequest *request) {

return [[request URL] absoluteString];

}

@implementation AFImageCache

// write to cache if proper policy on NSURLRequest :

- (UIImage *)cachedImageForRequest:(NSURLRequest *)request {

switch ([request cachePolicy]) {

case NSURLRequestReloadIgnoringCacheData:

case NSURLRequestReloadIgnoringLocalAndRemoteCacheData:

return nil;

default:

break;

}

return [self objectForKey:AFImageCacheKeyFromURLRequest(request)];

}

// read from cache :

- (void)cacheImage:(UIImage *)image

forRequest:(NSURLRequest *)request {

if (image && request) {

[self setObject:image forKey:AFImageCacheKeyFromURLRequest(request)];

}

}


AFImageCache是NSCache的私有实现,它把所有可访问的UIImage对象存入NSCache中,并控制着UIImage对象应该在何时释放,如果UIImage对象释放的时候你希望去做一些监听操作,你可以实现NSCacheDelegate的 cache:willEvictObject 代理方法。Matt Thompson已经谦虚的告诉我在AFNetworking2.1版本中可通过setSharedImageCache方法来配置AFImageCache,这里是 AFN2.2.1中的UIImageView+AFNetworking文档。


NSURLCache


AFNetworking使用了NSURLConnection,它利用了iOS原生的缓存机制,并且NSURLCache缓存了服务器返回的NSURLRespone对象。NSURLCache 的shareCache方法是默认开启的,你可以利用它来获取每一个NSURLConnection对象的URL内容。让人不爽的是,它的默认配置是缓存到内存而且并没有写入到磁盘。为了tame the beast(驯服野兽?不太懂),增加可持续性,你可以在AppDelegate中简单地声明一个共享的NSURLCache对象,像这样:


NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:2 * 1024 * 1024

diskCapacity:100 * 1024 * 1024

diskPath:nil];

[NSURLCache setSharedURLCache:sharedCache];


设置NSURLRequest对象的缓存策略


NSURLCache 将对每一个NSURLRequest对象遵守缓存策略(NSURLRequestCachePolicy),策略如下所示:


  • NSURLRequestUseProtocolCachePolicy                默认的缓存策略,对特定的URL请求使用网络协议中实现的缓存逻辑


  • NSURLRequestReloadIgnoringLocalCacheData          忽略本地缓存,重新请请求


  • NSURLRequestReloadIgnoringLocalAndRemoteCacheData 忽略本地和远程缓存,重新请求


  • NSURLRequestReturnCacheDataElseLoad               有缓存则从中加载,如果没有则去请求


  • NSURLRequestReturnCacheDataDontLoad               无网络状态下不去请求,一直加载本地缓存数据无论其是否存在


  • NSURLRequestReloadRevalidatingCacheData           默从原始地址确认缓存数据的合法性之后,缓存数据才可使用,否则请求原始地址


用NSURLCache缓存数据到磁盘


Cache-Control HTTP Header


Cache-Controlheader或Expires header存在于服务器返回的HTTP response header中,来用于客户端的缓存工作(前者优先级要高于后者),这里面有很多地方需要注意,Cache-Control可以拥有被定义为类似max-age的参数(在更新响应之前要缓存多长时间), public/private 访问或者是non-cache(不缓存响应数据),这里对HTTP cache headers进行了很好的介绍。


继承并控制NSURLCache


如果你想跳过Cache-Control,并且想要自己来制定规则读写一个带有NSURLResponse对象的NSURLCache,你可以继承NSURLCache。下面有个例子,使用 CACHE_EXPIRES来判断在获取源数据之前对缓存数据保留多长时间.(感谢 Mattt Thompson的回复)


@interface CustomURLCache : NSURLCache

static NSString * const CustomURLCacheExpirationKey = @"CustomURLCacheExpiration";

static NSTimeInterval const CustomURLCacheExpirationInterval = 600;

@implementation CustomURLCache

+ (instancetype)standardURLCache {

static CustomURLCache *_standardURLCache = nil;

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

_standardURLCache = [[CustomURLCache alloc]

initWithMemoryCapacity:(2 * 1024 * 1024)

diskCapacity:(100 * 1024 * 1024)

diskPath:nil];

}

return _standardURLCache;

}

#pragma mark - NSURLCache

- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request {

NSCachedURLResponse *cachedResponse = [super cachedResponseForRequest:request];

if (cachedResponse) {

NSDate* cacheDate = cachedResponse.userInfo[CustomURLCacheExpirationKey];

NSDate* cacheExpirationDate = [cacheDate dateByAddingTimeInterval:CustomURLCacheExpirationInterval];

if ([cacheExpirationDate compare:[NSDate date]] == NSOrderedAscending) {

[self removeCachedResponseForRequest:request];

return nil;

}

}

}

return cachedResponse;

}

- (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse

forRequest:(NSURLRequest *)request

{

NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:cachedResponse.userInfo];

userInfo[CustomURLCacheExpirationKey] = [NSDate date];

NSCachedURLResponse *modifiedCachedResponse = [[NSCachedURLResponse alloc] initWithResponse:cachedResponse.response data:cachedResponse.data userInfo:userInfo storagePolicy:cachedResponse.storagePolicy];

[super storeCachedResponse:modifiedCachedResponse forRequest:request];

}

@end


现在你有了属于自己的NSURLCache的子类,不要忘了在AppDelegate中初始化并且使用它。


在缓存之前重写NSURLResponse


-connection:willCacheResponse 代理方法是在被缓存之前用于截断和编辑由NSURLConnection创建的NSURLCacheResponse的地方。 对NSURLCacheResponse进行处理并返回一个可变的拷贝对象(代码来自NSHipster blog)


- (NSCachedURLResponse *)connection:(NSURLConnection *)connection

willCacheResponse:(NSCachedURLResponse *)cachedResponse {

NSMutableDictionary *mutableUserInfo = [[cachedResponse userInfo] mutableCopy];

NSMutableData *mutableData = [[cachedResponse data] mutableCopy];

NSURLCacheStoragePolicy storagePolicy = NSURLCacheStorageAllowedInMemoryOnly;

// ...

return [[NSCachedURLResponse alloc] initWithResponse:[cachedResponse response]

data:mutableData

userInfo:mutableUserInfo

storagePolicy:storagePolicy];

}

// If you do not wish to cache the NSURLCachedResponse, just return nil from the delegate function:

- (NSCachedURLResponse *)connection:(NSURLConnection *)connection

willCacheResponse:(NSCachedURLResponse *)cachedResponse {

return nil;

}


禁用NSURLCache


不想使用NSURLCache?不为所动?好吧,你可以禁用NSURLCache,只需要将内存和磁盘空间设置为0就行了.


NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0

diskCapacity:0

diskPath:nil];

[NSURLCache setSharedURLCache:sharedCache];


总结


我写这篇博客的目的是为iOS社区贡献绵薄之力,并总结了我是如何来处理关于AFNetworking缓存问题的。我们有个内部App在加载了大量图片后,出现了内存和性能问题,而我的主要职责是诊断这个App的缓存行为,在研究过程中,我在网上搜索了很多资料并且做了很多调试,在我汇总之后就写到了这篇博客中,我希望这篇文章可以为开发者使用AFNetworking时提供一些帮助,真心希望对你们有用!



微信号:CocoaChinabbs


▲长按二维码“识别”关注即可免费学习 iOS 开发

月薪十万、出任CEO、赢娶白富美、走上人生巅峰不是梦

--------------------------------------

商务合作QQ:2408167315

投稿邮箱:[email protected]

推荐文章
读书小分队  ·  想跟我跨年吗
7 年前
心理测试  ·  从恋爱到上床需要多久的时间?
7 年前
二更食堂  ·  姑娘,多谈几场恋爱再结婚
7 年前
二更食堂  ·  12星座最撩人的地方是什么
7 年前