源碼分析SDWebImage

SDWebImage是一個開源的第三方庫,它提供了UIImageView的一個分類,以支持從遠程服務器下載并緩存圖片的功能。它具有以下功能:

提供UIImageView的一個分類,以支持網絡圖片的加載與緩存管理

一個異步的圖片加載器

一個異步的內存+磁盤圖片緩存

支持GIF圖片

支持WebP圖片

后臺圖片解壓縮處理

確保同一個URL的圖片不被下載多次

確保虛假的URL不會被反復加載

確保下載及緩存時,主線程不被阻塞

從github上對SDWebImage使用情況就可以看出,SDWebImage在圖片下載及緩存的處理方面還是很被認可的。在本文中,我們

主要從源碼的角度來分析一下SDWebImage的實現機制。討論的內容將主要集中在圖片的下載及緩存,而不包含對GIF圖片及WebP圖片的支持操作。

下載

在SDWebImage中,圖片的下載是由SDWebImageDownloader類來完成的。它是一個異步下載器,并對圖像加載做了優化處理。下面我們就來看看它的具體實現。

下載選項

在下載的過程中,程序會根據設置的不同的下載選項,而執行不同的操作。下載選項由枚舉SDWebImageDownloaderOptions定義,具體如下

typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {SDWebImageDownloaderLowPriority =1<<0,SDWebImageDownloaderProgressiveDownload=1<<1,// 默認情況下請求不使用NSURLCache,如果設置該選項,則以默認的緩存策略來使用NSURLCacheSDWebImageDownloaderUseNSURLCache=1<<2,// 如果從NSURLCache緩存中讀取圖片,則使用nil作為參數來調用完成blockSDWebImageDownloaderIgnoreCachedResponse=1<<3,// 在iOS4+系統上,允許程序進入后臺后繼續下載圖片。該操作通過向系統申請額外的時間來完成后臺下載。如果后臺任務終止,則操作會被取消SDWebImageDownloaderContinueInBackground=1<<4,// 通過設置NSMutableURLRequest.HTTPShouldHandleCookies=YES來處理存儲在NSHTTPCookieStore中的cookieSDWebImageDownloaderHandleCookies=1<<5,// 允許不受信任的SSL證書。主要用于測試目的。SDWebImageDownloaderAllowInvalidSSLCertificates=1<<6,// 將圖片下載放到高優先級隊列中SDWebImageDownloaderHighPriority=1<<7,};

可以看出,這些選項主要涉及到下載的優先級、緩存、后臺任務執行、cookie處理以認證幾個方面。

下載順序

SDWebImage的下載操作是按一定順序來處理的,它定義了兩種下載順序,如下所示

typedefNS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {// 以隊列的方式,按照先進先出的順序下載。這是默認的下載順序SDWebImageDownloaderFIFOExecutionOrder,// 以棧的方式,按照后進先出的順序下載。SDWebImageDownloaderLIFOExecutionOrder};

下載管理器

SDWebImageDownloader下載管理器是一個單例類,它主要負責圖片的下載操作的管理。圖片的下載是放在一個NSOperationQueue操作隊列中來完成的,其聲明如下:

@property(strong, nonatomic) NSOperationQueue*downloadQueue;

默認情況下,隊列最大并發數是6。如果需要的話,我們可以通過SDWebImageDownloader類的maxConcurrentDownloads屬性來修改。

所有下載操作的網絡響應序列化處理是放在一個自定義的并行調度隊列中來處理的,其聲明及定義如下:

@property(SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;- (id)init {if((self= [superinit])) {..._barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue",DISPATCH_QUEUE_CONCURRENT);...}returnself;}

每一個圖片的下載都會對應一些回調操作,如下載進度回調,下載完成回調等,這些回調操作是以block形式來呈現,為此在SDWebImageDownloader.h中定義了幾個block,如下所示:

// 下載進度typedefvoid(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize);// 下載完成typedefvoid(^SDWebImageDownloaderCompletedBlock)(UIImage *image, NSData *data, NSError *error, BOOL finished);// Header過濾typedefNSDictionary *(^SDWebImageDownloaderHeadersFilterBlock)(NSURL *url, NSDictionary *headers);

圖片下載的這些回調信息存儲在SDWebImageDownloader類的URLCallbacks屬性中,該屬性是一個字典,key是圖片的URL地址,value則是一個數組,包含每個圖片的多組回調信息。由于我們允許多個圖片同時下載,因此可能會有多個線程同時操作URLCallbacks屬性。為了保證URLCallbacks操作(添加、刪除)的線程安全性,SDWebImageDownloader將這些操作作為一個個任務放到barrierQueue隊列中,并設置屏障來確保同一時間只有一個線程操作URLCallbacks屬性,我們以添加操作為例,如下代碼所示:

- (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock andCompletedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageNoParamsBlock)createCallback {? ? ...// 1. 以dispatch_barrier_sync操作來保證同一時間只有一個線程能對URLCallbacks進行操作dispatch_barrier_sync(self.barrierQueue, ^{...// 2. 處理同一URL的同步下載請求的單個下載NSMutableArray *callbacksForURL =self.URLCallbacks[url];NSMutableDictionary *callbacks = [NSMutableDictionarynew];if(progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];if(completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];[callbacksForURL addObject:callbacks];self.URLCallbacks[url] = callbacksForURL;...? ? });}

整個下載管理器對于下載請求的管理都是放在

downloadImageWithURL:options:progress:completed:方法里面來處理的,該方法調用了上面所提到的

addProgressCallback:andCompletedBlock:forURL:createCallback:方法來將請求的信息存入管

理器中,同時在創建回調的block中創建新的操作,配置之后將其放入downloadQueue操作隊列中,最后方法返回新創建的操作。其具體實現如

下:

- (id )downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {...[self addProgressCallback:progressBlock andCompletedBlock:completedBlock forURL:url createCallback:^{...// 1. 創建請求對象,并根據options參數設置其屬性// 為了避免潛在的重復緩存(NSURLCache + SDImageCache),如果沒有明確告知需要緩存,則禁用圖片請求的緩存操作NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];...// 2. 創建SDWebImageDownloaderOperation操作對象,并進行配置// 配置信息包括是否需要認證、優先級operation = [[wself.operationClass alloc] initWithRequest:requestoptions:optionsprogress:^(NSInteger receivedSize, NSInteger expectedSize) {// 3. 從管理器的callbacksForURL中找出該URL所有的進度處理回調并調用...for(NSDictionary *callbacksincallbacksForURL) {SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey];if(callback) callback(receivedSize, expectedSize);}}completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {// 4. 從管理器的callbacksForURL中找出該URL所有的完成處理回調并調用,// 如果finished為YES,則將該url對應的回調信息從URLCallbacks中刪除...if(finished) {[sself removeCallbacksForURL:url];}for(NSDictionary *callbacksincallbacksForURL) {SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];if(callback) callback(image, data, error, finished);}}cancelled:^{// 5. 取消操作將該url對應的回調信息從URLCallbacks中刪除SDWebImageDownloader *sself = wself;if(!sself)return;[sself removeCallbacksForURL:url];}];...// 6. 將操作加入到操作隊列downloadQueue中// 如果是LIFO順序,則將新的操作作為原隊列中最后一個操作的依賴,然后將新操作設置為最后一個操作[wself.downloadQueue addOperation:operation];if(wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {[wself.lastAddedOperation addDependency:operation];wself.lastAddedOperation = operation;}}];returnoperation;}

另外,每個下載操作的超時時間可以通過downloadTimeout屬性來設置,默認值為15秒。

下載操作

每個圖片的下載都是一個Operation操作。我們在上面分析過這個操作的創建及加入操作隊列的過程。現在我們來看看單個操作的具體實現。

SDWebImage定義了一個協議,即SDWebImageOperation作為圖片下載操作的基礎協議。它只聲明了一個cancel方法,用于取消操作。協議的具體聲明如下:

@protocol SDWebImageOperation- (void)cancel;@end

SDWebImage自定義了一個Operation類,即SDWebImageDownloaderOperation,它繼承自NSOperation,并采用了SDWebImageOperation協議。除了繼承而來的方法,該類只向外暴露了一個方法,即上面所用到的初始化方法initWithRequest:options:progress:completed:cancelled:。

對于圖片的下載,SDWebImageDownloaderOperation完全依賴于URL加載系統中的NSURLConnection類

(并未使用7.0以后的NSURLSession類)。我們先來分析一下SDWebImageDownloaderOperation類中對于圖片實際數

據的下載處理,即NSURLConnection各代理方法的實現。

首先,SDWebImageDownloaderOperation在分類中采用了NSURLConnectionDataDelegate協議,并實現了該協議的以下幾個方法:

- connection:didReceiveResponse:- connection:didReceiveData:- connectionDidFinishLoading:- connection:didFailWithError:- connection:willCacheResponse:- connectionShouldUseCredentialStorage:- connection:willSendRequestForAuthenticationChallenge:

我們在此不逐一分析每個方法的實現,就重點分析一下-connection:didReceiveData:方法。該方法的主要任務是接收數

據。每次接收到數據時,都會用現有的數據創建一個CGImageSourceRef對象以做處理。在首次獲取到數據時(width+height==0)

會從這些包含圖像信息的數據中取出圖像的長、寬、方向等信息以備使用。而后在圖片下載完成之前,會使用CGImageSourceRef對象創建一個圖片

對象,經過縮放、解壓縮操作后生成一個UIImage對象供完成回調使用。當然,在這個方法中還需要處理的就是進度信息。如果我們有設置進度回調的話,就

調用這個進度回調以處理當前圖片的下載進度。

注:縮放操作可以查看SDWebImageCompat文件中的SDScaledImageForKey函數;解壓縮操作可以查看SDWebImageDecoder文件+decodedImageWithImage方法

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {// 1. 附加數據[self.imageData appendData:data];if((self.options & SDWebImageDownloaderProgressiveDownload) &&self.expectedSize >0&&self.completedBlock) {// 2. 獲取已下載數據總大小constNSInteger totalSize =self.imageData.length;// 3. 更新數據源,我們需要傳入所有數據,而不僅僅是新數據CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)self.imageData,NULL);// 4. 首次獲取到數據時,從這些數據中獲取圖片的長、寬、方向屬性值if(width + height ==0) {CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource,0,NULL);if(properties) {NSInteger orientationValue = -1;CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);if(val) CFNumberGetValue(val, kCFNumberLongType, &height);...CFRelease(properties);// 5. 當繪制到Core Graphics時,我們會丟失方向信息,這意味著有時候由initWithCGIImage創建的圖片// 的方向會不對,所以在這邊我們先保存這個信息并在后面使用。orientation = [[selfclass] orientationFromPropertyValue:(orientationValue == -1?1: orientationValue)];}}// 6. 圖片還未下載完成if(width + height >0&& totalSize

我們前面說過SDWebImageDownloaderOperation類是繼承自NSOperation類。它沒有簡單的實現main方法,而是采用更加靈活的start方法,以便自己管理下載的狀態。

在start方法中,創建了我們下載所使用的NSURLConnection對象,開啟了圖片的下載,同時拋出一個下載開始的通知。當然,如果

我們期望下載在后臺處理,則只需要配置我們的下載選項,使其包含SDWebImageDownloaderContinueInBackground選

項。start方法的具體實現如下:

- (void)start {@synchronized (self) {// 管理下載狀態,如果已取消,則重置當前下載并設置完成狀態為YESif(self.isCancelled) {self.finished = YES;[selfreset];return;}#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0// 1. 如果設置了在后臺執行,則進行后臺執行if([selfshouldContinueWhenAppEntersBackground]) {__weak __typeof__ (self) wself =self;self.backgroundTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{...}}];}#endifself.executing = YES;self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:selfstartImmediately:NO];self.thread = [NSThread currentThread];}[self.connection start];if(self.connection) {if(self.progressBlock) {self.progressBlock(0, NSURLResponseUnknownLength);}// 2. 在主線程拋出下載開始通知dispatch_async(dispatch_get_main_queue(), ^{[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];});// 3. 啟動run loopif(floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_5_1) {CFRunLoopRunInMode(kCFRunLoopDefaultMode,10,false);}else{CFRunLoopRun();}// 4. 如果未完成,則取消連接if(!self.isFinished) {[self.connection cancel];[selfconnection:self.connection didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorTimedOut userInfo:@{NSURLErrorFailingURLErrorKey :self.request.URL}]];}}else{...}#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0if(self.backgroundTaskId != UIBackgroundTaskInvalid) {[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskId];self.backgroundTaskId = UIBackgroundTaskInvalid;}#endif}

當然,在下載完成或下載失敗后,需要停止當前線程的run

loop,清除連接,并拋出下載停止的通知。如果下載成功,則會處理完整的圖片數據,對其進行適當的縮放與解壓縮操作,以提供給完成回調使用。具體可參考

-connectionDidFinishLoading:與-connection:didFailWithError:的實現。

小結

下載的核心其實就是利用NSURLConnection對象來加載數據。每個圖片的下載都由一個Operation操作來完成,并將這些操作放到一個操作隊列中。這樣可以實現圖片的并發下載。

緩存

為了減少網絡流量的消耗,我們都希望下載下來的圖片緩存到本地,下次再去獲取同一張圖片時,可以直接從本地獲取,而不再從遠程服務器獲取。這樣做的另一個好處是提升了用戶體驗,用戶第二次查看同一幅圖片時,能快速從本地獲取圖片直接呈現給用戶。

SDWebImage提供了對圖片緩存的支持,而該功能是由SDImageCache類來完成的。該類負責處理內存緩存及一個可選的磁盤緩存。其中磁盤緩存的寫操作是異步的,這樣就不會對UI操作造成影響。

內存緩存及磁盤緩存

內存緩存的處理是使用NSCache對象來實現的。NSCache是一個類似于集合的容器。它存儲key-value對,這一點類似于

NSDictionary類。我們通常用使用緩存來臨時存儲短時間使用但創建昂貴的對象。重用這些對象可以優化性能,因為它們的值不需要重新計算。另外一

方面,這些對象對于程序來說不是緊要的,在內存緊張時會被丟棄。

磁盤緩存的處理則是使用NSFileManager對象來實現的。圖片存儲的位置是位于Cache文件夾。另外,SDImageCache還定義了一個串行隊列,來異步存儲圖片。

內存緩存與磁盤緩存相關變量的聲明及定義如下:

@interfaceSDImageCache ()@property(strong, nonatomic) NSCache*memCache;@property(strong, nonatomic) NSString*diskCachePath;@property(strong, nonatomic) NSMutableArray*customPaths;@property(SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t ioQueue;@end- (id)initWithNamespace:(NSString*)ns {if((self = [super init])) {NSString*fullNamespace= [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns];..._ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);...// Init the memory cache_memCache = [[NSCache alloc] init];_memCache.name = fullNamespace;// Init the disk cacheNSArray*paths= NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);_diskCachePath = [paths[0] stringByAppendingPathComponent:fullNamespace];dispatch_sync(_ioQueue, ^{_fileManager = [NSFileManager new];});...? ? }? ? return self;}

SDImageCache提供了大量方法來緩存、獲取、移除及清空圖片。而對于每個圖片,為了方便地在內存或磁盤中對它進行這些操作,我們需要

一個key值來索引它。在內存中,我們將其作為NSCache的key值,而在磁盤中,我們用這個key作為圖片的文件名。對于一個遠程服務器下載的圖

片,其url是作為這個key的最佳選擇了。我們在后面會看到這個key值的重要性。

存儲圖片

我們先來看看圖片的緩存操作,該操作會在內存中放置一份緩存,而如果確定需要緩存到磁盤,則將磁盤緩存操作作為一個task放到串行隊列中處

理。在iOS中,會先檢測圖片是PNG還是JPEG,并將其轉換為相應的圖片數據,最后將數據寫入到磁盤中(文件名是對key值做MD5摘要后的串)。緩

存操作的基礎方法是-storeImage:recalculateFromImage:imageData:forKey:toDisk,它的具體實現

如下:

- (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk {...// 1. 內存緩存,將其存入NSCache中,同時傳入圖片的消耗值[self.memCache setObject:image forKey:key cost:image.size.height * image.size.width * image.scale * image.scale];if(toDisk) {// 2. 如果確定需要磁盤緩存,則將緩存操作作為一個任務放入ioQueue中dispatch_async(self.ioQueue, ^{NSData *data = imageData;if(image && (recalculate || !data)) {#ifTARGET_OS_IPHONE// 3. 需要確定圖片是PNG還是JPEG。PNG圖片容易檢測,因為有一個唯一簽名。PNG圖像的前8個字節總是包含以下值:137 80 78 71 13 10 26 10// 在imageData為nil的情況下假定圖像為PNG。我們將其當作PNG以避免丟失透明度。而當有圖片數據時,我們檢測其前綴,確定圖片的類型BOOL imageIsPng = YES;if([imageData length] >= [kPNGSignatureData length]) {imageIsPng = ImageDataHasPNGPreffix(imageData);}if(imageIsPng) {data = UIImagePNGRepresentation(image);}else{data = UIImageJPEGRepresentation(image, (CGFloat)1.0);}#elsedata = [NSBitmapImageRep representationOfImageRepsInArray:image.representations usingType: NSJPEGFileType properties:nil];#endif}// 4. 創建緩存文件并存儲圖片if(data) {if(![_fileManager fileExistsAtPath:_diskCachePath]) {[_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];}[_fileManager createFileAtPath:[self defaultCachePathForKey:key] contents:data attributes:nil];}});}}

查詢圖片

如果我們想在內存或磁盤中查詢是否有key指定的圖片,則可以分別使用以下方法:

- (UIImage*)imageFromMemoryCacheForKey:(NSString*)key;- (UIImage*)imageFromDiskCacheForKey:(NSString*)key;

而如果只是想查看本地是否在key指定的圖片,則不管是在內存還是在磁盤上,則可以使用以下方法:

- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock {...// 1. 首先查看內存緩存,如果查找到,則直接回調doneBlock并返回UIImage *image = [selfimageFromDiskCacheForKey:key];if(image) {doneBlock(image, SDImageCacheTypeMemory);returnnil;}// 2. 如果內存中沒有,則在磁盤中查找。如果找到,則將其放到內存緩存,并調用doneBlock回調NSOperation *operation = [NSOperationnew];dispatch_async(self.ioQueue, ^{if(operation.isCancelled) {return;}@autoreleasepool {UIImage *diskImage = [selfdiskImageForKey:key];if(diskImage) {CGFloat cost = diskImage.size.height * diskImage.size.width * diskImage.scale * diskImage.scale;[self.memCache setObject:diskImage forKey:key cost:cost];}dispatch_async(dispatch_get_main_queue(), ^{doneBlock(diskImage, SDImageCacheTypeDisk);});}});returnoperation;}

移除圖片

圖片的移除操作則可以使用以下方法:

- (void)removeImageForKey:(NSString*)key;- (void)removeImageForKey:(NSString*)keywithCompletion:(SDWebImageNoParamsBlock)completion;- (void)removeImageForKey:(NSString*)keyfromDisk:(BOOL)fromDisk;- (void)removeImageForKey:(NSString*)keyfromDisk:(BOOL)fromDiskwithCompletion:(SDWebImageNoParamsBlock)completion;

我們可以選擇同時移除內存及磁盤上的圖片。

清理圖片

磁盤緩存圖片的清理操作可以分為完全清空和部分清理。完全清空操作是直接把緩存的文件夾移除,清空操作有以下兩個方法:

- (void)clearDisk;- (void)clearDiskOnCompletion:(SDWebImageNoParamsBlock)completion;

而部分清理則是根據我們設定的一些參數值來移除一些文件,這里主要有兩個指標:文件的緩存有效期及最大緩存空間大小。文件的緩存有效期可以通過

maxCacheAge屬性來設置,默認是1周的時間。如果文件的緩存時間超過這個時間值,則將其移除。而最大緩存空間大小是通過

maxCacheSize屬性來設置的,如果所有緩存文件的總大小超過這一大小,則會按照文件最后修改時間的逆序,以每次一半的遞歸來移除那些過早的文

件,直到緩存的實際大小小于我們設置的最大使用空間。清理的操作在-cleanDiskWithCompletionBlock:方法中,其實現如下:

- (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock {dispatch_async(self.ioQueue, ^{NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];// 1. 該枚舉器預先獲取緩存文件的有用的屬性NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURLincludingPropertiesForKeys:resourceKeysoptions:NSDirectoryEnumerationSkipsHiddenFileserrorHandler:NULL];NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.maxCacheAge];NSMutableDictionary *cacheFiles = [NSMutableDictionary dictionary];NSUInteger currentCacheSize =0;// 2. 枚舉緩存文件夾中所有文件,該迭代有兩個目的:移除比過期日期更老的文件;存儲文件屬性以備后面執行基于緩存大小的清理操作NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init];for(NSURL *fileURL in fileEnumerator) {NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:NULL];// 3. 跳過文件夾if([resourceValues[NSURLIsDirectoryKey] boolValue]) {continue;}// 4. 移除早于有效期的老文件NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];if([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {[urlsToDelete addObject:fileURL];continue;}// 5. 存儲文件的引用并計算所有文件的總大小,以備后用NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];currentCacheSize += [totalAllocatedSize unsignedIntegerValue];[cacheFiles setObject:resourceValues forKey:fileURL];}for(NSURL *fileURL in urlsToDelete) {[_fileManager removeItemAtURL:fileURL error:nil];}// 6.如果磁盤緩存的大小大于我們配置的最大大小,則執行基于文件大小的清理,我們首先刪除最老的文件if(self.maxCacheSize >0&& currentCacheSize >self.maxCacheSize) {// 7. 以設置的最大緩存大小的一半作為清理目標constNSUInteger desiredCacheSize =self.maxCacheSize /2;// 8. 按照最后修改時間來排序剩下的緩存文件NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrentusingComparator:^NSComparisonResult(id obj1, id obj2) {return[obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];}];// 9. 刪除文件,直到緩存總大小降到我們期望的大小for(NSURL *fileURL in sortedFiles) {if([_fileManager removeItemAtURL:fileURL error:nil]) {NSDictionary *resourceValues = cacheFiles[fileURL];NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];currentCacheSize -= [totalAllocatedSize unsignedIntegerValue];if(currentCacheSize < desiredCacheSize) {break;}}}}if(completionBlock) {dispatch_async(dispatch_get_main_queue(), ^{completionBlock();});}});}

小結

以上分析了圖片緩存操作,當然,除了上面講的幾個操作,SDImageCache類還提供了一些輔助方法。如獲取緩存大小、緩存中圖片的數量、

判斷緩存中是否存在某個key指定的圖片。另外,SDImageCache類提供了一個單例方法的實現,所以我們可以將其當作單例對象來處理。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內容