最近一個朋友問我如何自己做一個圖片緩存功能,說實話之前還真的沒有好好研究下,到底是如何在項目中做緩存的?以及如果需要更加機密的緩存如何做呢?正好項目中做了不錯的緩存功能,我就拋磚引玉了,大神請見諒,本人眼界有限,求拍磚。
首先明白一點,OC是存在一個緩存類的,關鍵我們是如何使用它的。
@interface NSCache : NSObject {
- (id)objectForKey:(id)key;
- (void)setObject:(id)obj forKey:(id)key; // 0 cost
上面的兩個紅色的方法,就告訴我們使用的方法就是通過類似設置字典鍵值對的形勢來保存數據的。所以可以簡化理解為緩存類就是一個自定義字典。我們要緩存的話,只需要把這個字典做成一個在在app所有頁面都唯一存在的字典就好了,所以需要把它設置為一個單例類,什麼是單例類大家自己去查查。
還有個一個點大家可能沒有考慮到,就是我們的沙盒中明明也存在一個叫做cache的文件夾,這個就是保存緩存數據的文件夾,所以我們要聯合起來用。
如何聯合來用?
大致思路就是我們保存到緩存單例類的同時也保存在沙盒緩存(保存到沙盒緩存中記得在主線程中執行)中,獲取的時候首先去獲取緩存類中的緩存數據,如果不存在在去沙盒緩存文件夾下去獲取,最後再找不到才算不存在緩存數據。
說了一大通我們先看看AFNetworking源碼是如何緩存圖片的。
// UIImageView+AFNetworking.m // // Copyright (c) 2013-2014 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import "UIImageView+AFNetworking.h" #import#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) #import "AFHTTPRequestOperation.h"
//緩存類 @interface AFImageCache : NSCache @end #pragma mark -
//UIImageview類別 @interface UIImageView (_AFNetworking) @property (readwrite, nonatomic, strong, setter = af_setImageRequestOperation:) AFHTTPRequestOperation *af_imageRequestOperation; @end @implementation UIImageView (_AFNetworking) + (NSOperationQueue *)af_sharedImageRequestOperationQueue { static NSOperationQueue *_af_sharedImageRequestOperationQueue = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _af_sharedImageRequestOperationQueue = [[NSOperationQueue alloc] init]; _af_sharedImageRequestOperationQueue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount; }); return _af_sharedImageRequestOperationQueue; } - (AFHTTPRequestOperation *)af_imageRequestOperation { return (AFHTTPRequestOperation *)objc_getAssociatedObject(self, @selector(af_imageRequestOperation)); } - (void)af_setImageRequestOperation:(AFHTTPRequestOperation *)imageRequestOperation { objc_setAssociatedObject(self, @selector(af_imageRequestOperation), imageRequestOperation, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @end #pragma mark -
//圖片類別 @implementation UIImageView (AFNetworking) @dynamic imageResponseSerializer; + (id )sharedImageCache { static AFImageCache *_af_defaultImageCache = nil; static dispatch_once_t oncePredicate; dispatch_once(&oncePredicate, ^{ _af_defaultImageCache = [[AFImageCache alloc] init]; [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidReceiveMemoryWarningNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * __unused notification) { [_af_defaultImageCache removeAllObjects]; }]; }); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" return objc_getAssociatedObject(self, @selector(sharedImageCache)) ?: _af_defaultImageCache;
// 注意這一句,意思就是如果我們自定義cache有的話,就直接返回我們自定義的cache,沒有就返回系統內部的AFImageCache,一旦手動設置之後我們以後獲取的時候就返回自定義的
// 有些同學可能認為這個我們最初設置了確實存在,那下次再訪問不就沒了,注意這個objc_getAssociatedObject,類似於系統userdefault一旦設置了,除非你清理掉不然一直有
#pragma clang diagnostic pop } + (void)setSharedImageCache:(id)imageCache { objc_setAssociatedObject(self, @selector(sharedImageCache), imageCache, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (id )imageResponseSerializer { static id _af_defaultImageResponseSerializer = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _af_defaultImageResponseSerializer = [AFImageResponseSerializer serializer]; }); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" return objc_getAssociatedObject(self, @selector(imageResponseSerializer)) ?: _af_defaultImageResponseSerializer; #pragma clang diagnostic pop } - (void)setImageResponseSerializer:(id )serializer { objc_setAssociatedObject(self, @selector(imageResponseSerializer), serializer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } #pragma mark - //外部供我們使用的接口 - (void)setImageWithURL:(NSURL *)url { [self setImageWithURL:url placeholderImage:nil]; }
cept屬//內部實現第一步,添加一個accept屬性(規定通過能夠通過文件上傳進行提交的文件類型image類型)- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholderImage{ NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; [request addValue:@"image/*" forHTTPHeaderField:@"Accept"]; [self setImageWithURLRequest:request placeholderImage:placeholderImage success:nil failure:nil];}
//內部實現的關鍵,主要原理就是判斷單例緩存類裡面是否有此url對應的圖片(注意內部實現就是獲取cache中,沒有就真沒有了),如果存在就直接返給外部,沒有的話,就通過url請求去獲取,獲取到了之後再存入緩存(存入cache) - (void)setImageWithURLRequest:(NSURLRequest *)urlRequest placeholderImage:(UIImage *)placeholderImage success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure { [self cancelImageRequestOperation]; UIImage *cachedImage = [[[self class] sharedImageCache] cachedImageForRequest:urlRequest]; //獲取 if (cachedImage) { if (success) { success(nil, nil, cachedImage); } else { self.image = cachedImage; } self.af_imageRequestOperation = nil; } else { if (placeholderImage) { self.image = placeholderImage; } __weak __typeof(self)weakSelf = self; self.af_imageRequestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:urlRequest]; self.af_imageRequestOperation.responseSerializer = self.imageResponseSerializer; [self.af_imageRequestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { __strong __typeof(weakSelf)strongSelf = weakSelf; if ([[urlRequest URL] isEqual:[strongSelf.af_imageRequestOperation.request URL]]) { if (success) { success(urlRequest, operation.response, responseObject); } else if (responseObject) { strongSelf.image = responseObject; } if (operation == strongSelf.af_imageRequestOperation){ strongSelf.af_imageRequestOperation = nil; } } [[[strongSelf class] sharedImageCache] cacheImage:responseObject forRequest:urlRequest]; //存入 } failure:^(AFHTTPRequestOperation *operation, NSError *error) { __strong __typeof(weakSelf)strongSelf = weakSelf; if ([[urlRequest URL] isEqual:[strongSelf.af_imageRequestOperation.request URL]]) { if (failure) { failure(urlRequest, operation.response, error); } if (operation == strongSelf.af_imageRequestOperation){ strongSelf.af_imageRequestOperation = nil; } } }]; [[[self class] af_sharedImageRequestOperationQueue] addOperation:self.af_imageRequestOperation]; } } - (void)cancelImageRequestOperation { [self.af_imageRequestOperation cancel]; self.af_imageRequestOperation = nil; } @end #pragma mark - static inline NSString * AFImageCacheKeyFromURLRequest(NSURLRequest *request) { return [[request URL] absoluteString]; } @implementation AFImageCache
//緩存實現就是通過下面兩個方法來實現存取的 - (UIImage *)cachedImageForRequest:(NSURLRequest *)request { switch ([request cachePolicy]) { case NSURLRequestReloadIgnoringCacheData: case NSURLRequestReloadIgnoringLocalAndRemoteCacheData: return nil; default: break; } return [self objectForKey:AFImageCacheKeyFromURLRequest(request)]; } - (void)cacheImage:(UIImage *)image forRequest:(NSURLRequest *)request { if (image && request) { [self setObject:image forKey:AFImageCacheKeyFromURLRequest(request)]; } } @end #endif上面的緩存是AF自帶的