存 取 刪 路徑html
是在storeImage這個方法裏:緩存
將圖片儲存到內存和硬盤上網絡
-(void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk { if (!image || !key) { return; } // if memory cache is enabled if (self.shouldCacheImagesInMemory) { NSUInteger cost = SDCacheCostForImage(image); [self.memCache setObject:image forKey:key cost:cost]; } if (toDisk) { dispatch_async(self.ioQueue, ^{ NSData *data = imageData; // 若是image存在,可是須要從新計算(recalculate)或者data爲空 // 那就要根據image從新生成新的data // 不過要是連image也爲空的話,那就別存了 if (image && (recalculate || !data)) { #if TARGET_OS_IPHONE // 咱們須要判斷image是PNG仍是JPEG // PNG的圖片很容易檢測出來,由於它們有一個特定的標示 (http://www.w3.org/TR/PNG-Structure.html) // PNG圖片的前8個字節不準符合下面這些值(十進制表示) // 137 80 78 71 13 10 26 10 // 若是imageData爲空l (舉個例子,好比image在下載後須要transform,那麼就imageData就會爲空) // 而且image有一個alpha通道, 咱們將該image看作PNG以免透明度(alpha)的丟失(由於JPEG沒有透明色) int alphaInfo = CGImageGetAlphaInfo(image.CGImage);// 獲取image中的透明信息 // 該image中確實有透明信息,就認爲image爲PNG BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone || alphaInfo == kCGImageAlphaNoneSkipFirst || alphaInfo == kCGImageAlphaNoneSkipLast); BOOL imageIsPng = hasAlpha; // 可是若是咱們已經有了imageData,咱們就能夠直接根據data中前幾個字節判斷是否是PNG if ([imageData length] >= [kPNGSignatureData length]) { // ImageDataHasPNGPreffix就是爲了判斷imageData前8個字節是否是符合PNG標誌 imageIsPng = ImageDataHasPNGPreffix(imageData); } // 若是image是PNG格式,就是用UIImagePNGRepresentation將其轉化爲NSData,不然按照JPEG格式轉化,而且壓縮質量爲1,即無壓縮 if (imageIsPng) { data = UIImagePNGRepresentation(image); } else { data = UIImageJPEGRepresentation(image, (CGFloat)1.0); } #else // 固然,若是不是在iPhone平臺上,就使用下面這個方法。不過不在咱們研究範圍以內 data = [NSBitmapImageRep representationOfImageRepsInArray:image.representations usingType: NSJPEGFileType properties:nil]; #endif } // 獲取到須要存儲的data後,下面就要用fileManager進行存儲了 if (data) { // 首先判斷disk cache的文件路徑是否存在,不存在的話就建立一個 // disk cache的文件路徑是存儲在_diskCachePath中的 if (![_fileManager fileExistsAtPath:_diskCachePath]) { [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL]; } // 根據image的key(通常狀況下理解爲image的url)組合成最終的文件路徑 // 上面那個生成的文件路徑只是一個文件目錄,就跟/cache/images/img1.png和cache/images/的區別同樣 NSString *cachePathForKey = [self defaultCachePathForKey:key]; // 這個url可不是網絡端的url,而是file在系統路徑下的url // 好比/foo/bar/baz --------> file:///foo/bar/baz NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey]; // 根據存儲的路徑(cachePathForKey)和存儲的數據(data)將其存放到iOS的文件系統 [_fileManager createFileAtPath:cachePathForKey contents:data attributes:nil]; // disable iCloud backup if (self.shouldDisableiCloud) { [fileURL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:nil]; } } }); } }
內存緩存使用NSCache的objectForKey取數據:異步
- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key { return [self.memCache objectForKey:key]; }
磁盤取數據 不斷用 dataWithContentsOfFile來試數據是否在key對應的路徑中async
- (UIImage *)imageFromDiskCacheForKey:(NSString *)key { // First check the in-memory cache... UIImage *image = [self imageFromMemoryCacheForKey:key]; if (image) { return image; } // Second check the disk cache... UIImage *diskImage = [self diskImageForKey:key]; if (diskImage && self.shouldCacheImagesInMemory) { NSUInteger cost = SDCacheCostForImage(diskImage); [self.memCache setObject:diskImage forKey:key cost:cost]; } return diskImage; }
看其中最長的一個:函數
// 實現了一個簡單的緩存清除策略:清除修改時間最先的file - (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock { dispatch_async(self.ioQueue, ^{ // 這兩個變量主要是爲了下面生成NSDirectoryEnumerator準備的 // 一個是記錄遍歷的文件目錄,一個是記錄遍歷須要預先獲取文件的哪些屬性 NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES]; NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey]; // 遞歸地遍歷diskCachePath這個文件夾中的全部目錄,此處不是直接使用diskCachePath,而是使用其生成的NSURL // 此處使用includingPropertiesForKeys:resourceKeys,這樣每一個file的resourceKeys對應的屬性也會在遍歷時預先獲取到 // NSDirectoryEnumerationSkipsHiddenFiles表示不遍歷隱藏文件 NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL includingPropertiesForKeys:resourceKeys options:NSDirectoryEnumerationSkipsHiddenFiles errorHandler:NULL]; // 獲取文件的過時時間,SDWebImage中默認是一個星期 // 不過這裏雖然稱*expirationDate爲過時時間,可是實質上並非這樣。 // 實際上是這樣的,好比在2015/12/12/00:00:00最後一次修改文件,對應的過時時間應該是 // 2015/12/19/00:00:00,不過如今時間是2015/12/27/00:00:00,我先將當前時間減去1個星期,獲得 // 2015/12/20/00:00:00,這個時間纔是咱們函數中的expirationDate。 // 用這個expirationDate和最後一次修改時間modificationDate比較看誰更晚就行。 NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.maxCacheAge]; // 用來存儲對應文件的一些屬性,好比文件所需磁盤空間 NSMutableDictionary *cacheFiles = [NSMutableDictionary dictionary]; // 記錄當前已經使用的磁盤緩存大小 NSUInteger currentCacheSize = 0; // 在緩存的目錄開始遍歷文件. 這次遍歷有兩個目的: // // 1. 移除過時的文件 // 2. 同時存儲每一個文件的屬性(好比該file是不是文件夾、該file所需磁盤大小,修改時間) NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init]; for (NSURL *fileURL in fileEnumerator) { NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:NULL]; // 當前掃描的是目錄,就跳過 if ([resourceValues[NSURLIsDirectoryKey] boolValue]) { continue; } // 移除過時文件 // 這裏判斷過時的方式:對比文件的最後一次修改日期和expirationDate誰更晚,若是expirationDate更晚,就認爲該文件已通過期,具體解釋見上面 NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey]; if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) { [urlsToDelete addObject:fileURL]; continue; } // 計算當前已經使用的cache大小, // 並將對應file的屬性存到cacheFiles中 NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey]; currentCacheSize += [totalAllocatedSize unsignedIntegerValue]; [cacheFiles setObject:resourceValues forKey:fileURL]; } for (NSURL *fileURL in urlsToDelete) { // 根據須要移除文件的url來移除對應file [_fileManager removeItemAtURL:fileURL error:nil]; } // 若是咱們當前cache的大小已經超過了容許配置的緩存大小,那就刪除已經緩存的文件。 // 刪除策略就是,首先刪除修改時間更早的緩存文件 if (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) { // 直接將當前cache大小降到容許最大的cache大小的通常 const NSUInteger desiredCacheSize = self.maxCacheSize / 2; // 根據文件修改時間來給全部緩存文件排序,按照修改時間越早越在前的規則排序 NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent usingComparator:^NSComparisonResult(id obj1, id obj2) { return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]]; }]; // 每次刪除file後,就計算此時的cache的大小 // 若是此時的cache大小已經降到指望的大小了,就中止刪除文件了 for (NSURL *fileURL in sortedFiles) { if ([_fileManager removeItemAtURL:fileURL error:nil]) { // 獲取該文件對應的屬性 NSDictionary *resourceValues = cacheFiles[fileURL]; // 根據resourceValues獲取該文件所需磁盤空間大小 NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey]; // 計算當前cache大小 currentCacheSize -= [totalAllocatedSize unsignedIntegerValue]; if (currentCacheSize < desiredCacheSize) { break; } } } } // 若是有completionBlock,就在主線程中調用 if (completionBlock) { dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(); }); } }); }
// 簡單封裝了cachePathForKey:inPath - (NSString *)defaultCachePathForKey:(NSString *)key { return [self cachePathForKey:key inPath:self.diskCachePath]; } // cachePathForKey:inPath - (NSString *)cachePathForKey:(NSString *)key inPath:(NSString *)path { // 根據傳入的key建立最終要存儲時的文件名 NSString *filename = [self cachedFileNameForKey:key]; // 將存儲的文件路徑和文件名綁定在一塊兒,做爲最終的存儲路徑 return [path stringByAppendingPathComponent:filename]; } // cachedFileNameForKey: - (NSString *)cachedFileNameForKey:(NSString *)key { const char *str = [key UTF8String]; if (str == NULL) { str = ""; } // 使用了MD5進行加密處理 // 開闢一個16字節(128位:md5加密出來就是128bit)的空間 unsigned char r[CC_MD5_DIGEST_LENGTH]; // 官方封裝好的加密方法 // 把str字符串轉換成了32位的16進制數列(這個過程不可逆轉) 存儲到了r這個空間中 CC_MD5(str, (CC_LONG)strlen(str), r); // 最終生成的文件名就是 "md5碼"+".文件類型" NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@", r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10], r[11], r[12], r[13], r[14], r[15], [[key pathExtension] isEqualToString:@""] ? @"" : [NSString stringWithFormat:@".%@", [key pathExtension]]]; return filename; }