您的位置:首页 > 移动开发 > IOS开发

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

2015-04-24 13:31 393 查看
大致翻译自:How Does Caching Work in AFNetworking? : AFImageCache & NSUrlCache Explained

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

AFNetworking
实际上用了2种不同的缓存机制:

AFImageCache
AFNetworking
专用的memory-only图像缓存,继承自NSCache

NSURLCache
NSURLConnection's
的默认的URL缓存机制,用来存储
NSURLResponse
对象:默认是
in-memory
缓存,也可以配置为
on-disk
的持久化缓存。

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

AFImageCache是怎样工作的?

AFImageCache
UIImageView+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)];
}
}


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

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

NSURLCache是如何工作的?

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

Enabled by Default, but Needs a Hand

NSURLCache
sharedCache
是默认启用的,被用来获取任何
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 : 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初始化时使用它:

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的人提供额外的信息。我希望这对你有所帮助。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  缓存 iOS cache AFNetworki