SDWebImage *底層探究 (二)

圖片加載:

[cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"] 
                  placeholderImage:[UIImage imageNamed:@"placeholder.png"]];
# 其實(shí)是通過 SDWebImageManager類進(jìn)行協(xié)調(diào),調(diào)用 SDImageCache與 SDWebImageDownloader來實(shí)現(xiàn)圖片的緩存查詢與網(wǎng)絡(luò)下載的。

1. SDImageCache

該類維護(hù)了一個(gè)內(nèi)存緩存與一個(gè)可選的磁盤緩存. 同時(shí), 磁盤緩存的寫操作是異步的, 所以他不會(huì)對(duì)UI造成不必要的影響.

*每次查詢圖片時(shí), 首先會(huì)根據(jù)圖片的URL對(duì)應(yīng)的key值檢測(cè)內(nèi)存中是否有對(duì)應(yīng)的圖片:
@ 如果有則直接返回;
@ 如果沒有則在ioQueue中去磁盤中查找;
其key是根據(jù)URL生成的MD5值, 找到圖片緩存在內(nèi)存中, 然后把圖片返回.

- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock {
     if (!doneBlock) { 
            return nil; 
     }
     if (!key) { 
            doneBlock(nil, SDImageCacheTypeNone); 
            return nil; 
     } 

    // 首先檢查內(nèi)存緩存(查詢是同步的),如果查找到,則直接回調(diào) doneBlock 并返回 
    UIImage *image = [self imageFromMemoryCacheForKey:key]; 
    if (image) {
        doneBlock(image, SDImageCacheTypeMemory); 
        return nil;
     } 

    NSOperation *operation = [NSOperation new]; 
    dispatch_async(self.ioQueue, ^{ 
        if (operation.isCancelled) { 
            return; 
        } 
    // 創(chuàng)建自動(dòng)釋放池,內(nèi)存及時(shí)釋放 
    @autoreleasepool { 
        // 檢查磁盤緩存(查詢是異步的),如果查找到,則將其放到內(nèi)存緩存,并調(diào)用 doneBlock 回調(diào)
        UIImage *diskImage = [self diskImageForKey:key]; 
        if (diskImage) { 
            NSUInteger cost = SDCacheCostForImage(diskImage); 
            // 緩存至內(nèi)存(NSCache)中 
            [self.memCache setObject:diskImage forKey:key cost:cost];
        } 
        // 返回主線程設(shè)置圖片 
        dispatch_async(dispatch_get_main_queue(), ^{ 
            doneBlock(diskImage, SDImageCacheTypeDisk); 
        });
     }
   }); 
  return operation;
}

2. NSCache

NSCache 是蘋果官方提供的緩存類,用法與 NSMutableDictionary 的用法很相似,在 SDWebImage 和 AFNetworking 中,使用它來管理緩存。同樣是以 key-value 的形式進(jìn)行存儲(chǔ),那么 NSCache 與 NSMutableDictionary 等集合類的區(qū)別或者說優(yōu)勢(shì)又是哪些呢?

  • NSCache 類結(jié)合了各種自動(dòng)刪除策略,以確保不會(huì)占用過多的系統(tǒng)內(nèi)存。如果其它應(yīng)用需要內(nèi)存時(shí),系統(tǒng)自動(dòng)執(zhí)行這些策略。當(dāng)調(diào)用這些策略時(shí),會(huì)從緩存中刪除一些對(duì)象,以最大限度減少內(nèi)存的占用
  • NSCache 是線程安全的,我們可以在不同的線程中添加、刪除和查詢緩存中的對(duì)象,而不需要鎖定緩存區(qū)域
  • 不像 NSMutableDictionary 對(duì)象,NSCache 對(duì)象并不會(huì)拷貝鍵(key),而是會(huì)強(qiáng)引用它

要點(diǎn)

  1. 在開發(fā)者自己編寫加鎖代碼的前提下, 多個(gè)線程便可以同時(shí)訪問NSCache
  2. NSCache對(duì)象不拷貝鍵的原因在于: 很多時(shí)候, 鍵都是由不支持拷貝操作的對(duì)象來充當(dāng)?shù)? 所以說, 在不支持拷貝操作的情況下, 該類用起來比字典更方便.
  3. 可以給NSCache對(duì)象設(shè)置上限, 用以限制緩存中的對(duì)象總個(gè)數(shù), 而這些尺度則定義了緩存刪減中對(duì)象的時(shí)間. 但是絕對(duì)不要把這些尺度當(dāng)成靠山, 他們僅對(duì)于NSCache起指導(dǎo)作用.
  4. 將NSPurgeableData與NSCache搭配使用, 可實(shí)現(xiàn)自動(dòng)清除數(shù)據(jù)的功能, 也就是說, 當(dāng)NSPurgeableData對(duì)象所占內(nèi)存為系統(tǒng)所丟棄時(shí), 該對(duì)象自身也會(huì)從緩存中移除.
  5. 如果緩存使用得當(dāng), 那么應(yīng)用程序的響應(yīng)速度就能提高. 只有那種(重新計(jì)算起來哼費(fèi)時(shí)的)數(shù)據(jù), 才值得放入緩存, 比如那些需要從網(wǎng)絡(luò)獲取或者從磁盤讀取的數(shù)據(jù).
  6. 內(nèi)存查詢是同步, 磁盤查詢是異步.

3. 磁盤

磁盤緩存的處理則是使用NSFileManager對(duì)象來實(shí)現(xiàn)的. 默認(rèn)以com.hackemist.SDWebImageCache.default為磁盤的緩存命名空間, 程序運(yùn)行后, 可以在程序的文件夾Library/Caches/default/com.hackemist.SDWebImageCache.default下看到一些緩存文件. 另外, SDImageCache還定義了一個(gè)串行隊(duì)列, 來異存儲(chǔ)圖片.

在磁盤查詢的時(shí)候, 會(huì)在后臺(tái)將NSData轉(zhuǎn)場(chǎng)UIImage, 并完成相關(guān)的解碼工作:

- (UIImage *)diskImageForKey:(NSString *)key { 
    NSData *data = [self diskImageDataBySearchingAllPathsForKey:key]; 
    if (data) {   
        UIImage *image = [UIImage sd_imageWithData:data]; 
        image = [self scaledImageForKey:key image:image]; 
        if (self.shouldDecompressImages) {
             image = [UIImage decodedImageWithImage:image]; 
        }
        return image;
    } else {
       return nil;
    }
}

4. 存儲(chǔ)圖片

當(dāng)下載玩圖片后, 會(huì)先將圖片保存到NSCache中, 并把圖片像素大小作為該對(duì)象的cost值, 同時(shí)如果需要保存到硬盤, 會(huì)先判斷圖片的格式, PNG 和JPEG, 并保存對(duì)應(yīng)的NSData到緩存路徑中, 文件名為URL 的MD5值:

- (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk { 
    ...
    // 內(nèi)存緩存,將其存入 NSCache 中,同時(shí)傳入圖片的消耗值,cost 為像素值(當(dāng)內(nèi)存受限或者所有緩存對(duì)象的總代價(jià)超過了最大允許的值時(shí),緩存會(huì)移除其中的一些對(duì)象) 
    [self.memCache setObject:image forKey:key cost:image.size.height * image.size.width * image.scale * image.scale]; 
    if (toDisk) { 
         // 如果確定需要磁盤緩存,則將緩存操作作為一個(gè)任務(wù)放入 ioQueue 中 
         dispatch_async(self.ioQueue, ^{ 
            // 構(gòu)建一個(gè) data,用來存儲(chǔ)到 disk 中,默認(rèn)值為 imageData 
            NSData *data = imageData; 
            if (image && (recalculate || !data)) {
#if TARGET_OS_IPHONE
                 // 需要確定圖片是 PNG 還是 JPEG。PNG 圖片容易檢測(cè),因?yàn)橛幸粋€(gè)唯一簽名。PNG 圖像的前 8 個(gè)字節(jié)總是包含以下值:137 80 78 71 13 10 26 10 // 在 imageData 為 nil 的情況下假定圖像為 PNG。我們將其當(dāng)作 PNG 以避免丟失透明度。而當(dāng)有圖片數(shù)據(jù)時(shí),我們檢測(cè)其前綴,確定圖片的類型 
                 BOOL imageIsPng = YES; 
                 if ([imageData length] >= [kPNGSignatureData length]) { 
                     imageIsPng = ImageDataHasPNGPreffix(imageData); 
                 }
                // 如果 image 是 PNG 格式,就是用 UIImagePNGRepresentation 將其轉(zhuǎn)化為 NSData,否則按照 JPEG 格式轉(zhuǎn)化,并且壓縮質(zhì)量為 1,即無壓縮 
                if (imageIsPng) { 
                     data = UIImagePNGRepresentation(image); 
                } else { 
                    data = UIImageJPEGRepresentation(image, (CGFloat)1.0); 
                }
#else
               data = [NSBitmapImageRep representationOfImageRepsInArray:image.representations usingType: NSJPEGFileType properties:nil];
#endif 
        } 
        // 創(chuàng)建緩存文件并存儲(chǔ)圖片(使用 fileManager) 
        if (data) {
             if (![_fileManager fileExistsAtPath:_diskCachePath]) { 
                    [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL]; 
              } 
            // 保存 data 到指定的路徑中
            [_fileManager createFileAtPath:[self defaultCachePathForKey:key] contents:data attributes:nil]; 
        } 
    }); 
  }
}

5. 清理圖片

SDImageCache 會(huì)在系統(tǒng)發(fā)出低內(nèi)存警告時(shí)釋放內(nèi)存,并且在程序進(jìn)入 UIApplicationWillTerminateNotification 時(shí),清理磁盤緩存,清理磁盤的機(jī)制是:

  1. 刪除過期的圖片,默認(rèn) 7 天過期,可以通過 maxCacheAge 修改過期天數(shù)。

  2. 如果緩存的數(shù)據(jù)大小超過設(shè)置的最大緩存 maxCacheSize,則會(huì)按照文件最后修改時(shí)間的逆序,以每次一半的遞歸來移除那些過早的文件,直到緩存的實(shí)際大小小于我們?cè)O(shè)置的最大使用空間,可以通過修改 maxCacheSize 來改變最大緩存大小。

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

       // 刪除文件,直到緩存總大小降到我們期望的大小 
       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(); }); 
  }
 });
}

http://itangqi.me/2016/03/23/the-notes-of-learning-sdwebimage-three/
http://itangqi.me/2016/03/24/the-notes-of-learning-sdwebimage-four/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 圖片下載的這些回調(diào)信息存儲(chǔ)在SDWebImageDownloader類的URLOperations屬性中,該屬性是...
    怎樣m閱讀 2,677評(píng)論 0 1
  • 下載 下載管理器 SDWebImageDownLoader作為一個(gè)單例來管理圖片的下載操作。圖片的下載是放在一個(gè)N...
    wind_dy閱讀 1,661評(píng)論 0 1
  • 技術(shù)無極限,從菜鳥開始,從源碼開始。 由于公司目前項(xiàng)目還是用OC寫的項(xiàng)目,沒有升級(jí)swift 所以暫時(shí)SDWebI...
    充滿活力的早晨閱讀 12,836評(píng)論 0 2
  • SDWebImage是一個(gè)開源的第三方庫,它提供了UIImageView的一個(gè)分類,以支持從遠(yuǎn)程服務(wù)器下載并緩存圖...
    devning閱讀 478評(píng)論 0 0
  • 第一篇第二篇大概是把下載圖片緩存圖片的這個(gè)邏輯走完了,里面涉及好多類。 羅列一下 UIView+WebCache ...
    充滿活力的早晨閱讀 843評(píng)論 0 1

友情鏈接更多精彩內(nèi)容