AFNetworking中的缓存是如何工作的?:对AFImageCache & NSUrlCache 解释

来源:互联网 发布:无锡淘宝影视 张飞飞 编辑:程序博客网 时间:2024/06/05 10:31

大致翻译自:How Does Caching Work in AFNetworking? : AFImageCache & NSUrlCache Explained

如果你是个在使用Mattt Thompson’s的AFNetworking的开发者,也许你会好奇或者困惑于缓存的机制和你如何调整它,已对自己有利。
AFNetworking实际上用了2种不同的缓存机制:

  • AFImageCacheAFNetworking专用的memory-only图像缓存,继承自NSCache
  • NSURLCacheNSURLConnection's的默认的URL缓存机制,用来存储NSURLResponse对象:默认是in-memory缓存,也可以配置为on-disk的持久化缓存。

为了了解每个缓存系统是怎么工作的,让我们来看看它们是怎么定义的:

AFImageCache是怎样工作的?

AFImageCacheUIImageView+AFNetworking类别的一部分。它是NSCache的子类,把URL字符串作为一个键(从输入的NSURLRequest对象获得)来存储UIImage对象。
AFImageCache 定义:

@interface AFImageCache : NSCache <AFImageCache>// singleton instantiation :+ (id <AFImageCache>)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)];    }}

AFImageCacheNSCache的private implementation。在UIImageView+AFNetworking类别之外,无法自定义。它将所有的UIImage对象存储到它的NSCache中。NSCache来控制UIImage对象什么时候被释放。如果你像监测images什么时候可以被释放掉,你可以实现NSCacheDelegatecache:willEvictObject方法。

编辑(03.14.14):Mattt Thompson已通知我,自AFNetworking 2.1,AFImageCache是可配置的。现在有个公有的方法setSharedImageCache。这是AFN 2.2.1 的说明UIImageView+AFNetworking specification。

NSURLCache是如何工作的?

由于AFNetworking使用了NSURLConnection,它充分利用了它原生的缓存机制NSURLCacheNSURLCache缓存通过NSURLConnection返回的的NSURLResponse对象。

Enabled by Default, but Needs a Hand

NSURLCachesharedCache是默认启用的,被用来获取任何NSURLConnection对象的URL内容。
不幸的是,it has a tendency to hog memory而且默认配置是不会直接写入到磁盘上。为了tame the beast,并增加可持续性,你可以在app代理中声明一个共享的NSURLCache,如下:

NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:2 * 1024 * 1024                                              diskCapacity:100 * 1024 * 1024                                              diskPath:nil];[NSURLCache setSharedURLCache:sharedCache];

这里我们声明了一个共享的NSURLCache,占2mb内存,100mb的磁盘空间。

在NSURLRequest对象设置缓存策略

NSURLCache会遵循每个NSURLRequest对象的缓存策略(NSURLRequestCachePolicy)。策略定义如下:

  • NSURLRequestUseProtocolCachePolicy:对特定的 URL 请求使用网络协议中实现的缓存逻辑。这是默认的策略。
  • NSURLRequestReloadIgnoringLocalCacheData:忽略本地缓存,重新加载
  • NSURLRequestReloadIgnoringLocalAndRemoteCacheData:忽略本地和远程缓存,重新加载
  • NSURLRequestReturnCacheDataElseLoad:无论缓存是否过期,先使用本地缓存数据。如果缓存中没有请求所对应的数据,那么从原始地址加载数据。
  • NSURLRequestReturnCacheDataDontLoad:无论缓存是否过期,先使用本地缓存数据。如果缓存中没有请求所对应的数据,那么放弃从原始地址加载数据,请求视为失败(即:“离线”模式)。
  • NSURLRequestReloadRevalidatingCacheData:从原始地址确认缓存数据的合法性后,缓存数据就可以使用,否则从原始地址加载。

使用NSURLCache缓存到磁盘上

Cache-Control HTTP Header

为了在客户端缓存,Cache-Control header或者Expires heade必须在服务器的HTTP的响应头中(Cache-Control header存在的优先级高于Expires header)。有许多情况要考虑到。 Cache Control可以有定义的参数,例如max-age(在更新响应之前缓存要多久), public / private access,或者是no-cache无缓存(不缓存响应)。这儿是对HTTP cache headers的简介。

Subclass NSURLCache for Ultimate Control

如果你想要绕过HTTP Cache-Control header的要求,想要定义你自己的读写给定NSURLResponse对象的NSURLCache规则,你可以继承NSURLCache
下面一个例子,该例子使用了CACHE_EXPIRES来标记在重新获取前要持有缓存对象多久:
(感谢Mattt Thompson的反馈和对代码的编辑)

@interface CustomURLCache : NSURLCachestatic 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初始化时使用它:

CustomURLCache *URLCache = [[CustomURLCache alloc] initWithMemoryCapacity:2 * 1024 * 1024                                                   diskCapacity:100 * 1024 * 1024                                                                 diskPath:nil];[NSURLCache setSharedURLCache:URLCache];

在缓存前重写NSURLResponse

在缓存前,NSURLConnection-connection:willCacheResponse代理方法是来拦截和编辑NSURLCachedResponse的地方。为了编辑NSURLCachedResponse,如下所示返回可编辑的副本(来自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,在你的appDelegate中,把共享的NSURLCache的内存和磁盘空间置为0即可:

NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0                                              diskCapacity:0                                              diskPath:nil];[NSURLCache setSharedURLCache:sharedCache];

总结

我写这篇博客是为了对iOS社区有益,我总结了所有与AFNetworking缓存有关的信息。我们内部有个app来加载大量的图片,它有一些内存和性能问题。我被安排来诊断app的缓存行为。在练习的过程中,我在网上发现了大量的信息,并做了大量的调试和测试。我希望我总结的信息,可以为其他使用AFNetworking的人提供额外的信息。我希望这对你有所帮助。

0 0