AFNetWorking是如何進行數據緩存的--之AFImageCache & NSURLCache 詳解
如果你是一個正在使用由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時提供一些幫助,真心希望對你們有用!
來自:http://www.cocoachina.com/ios/20161101/17906.html