【轉(zhuǎn)】SDWebImage源碼閱讀(五)

1. 前言


前面的代碼并沒有特意去講SDWebImage的緩存機制,主要是想單獨開一章節(jié)專門講解緩存。之前我們也遇到一些緩存的屬性和方法,比如storeImage、queryDiskCacheForKey、memCache等等。

SDWebImage的緩存分為兩個部分,一個內(nèi)存緩存,使用NSCache實現(xiàn),另一個就是硬盤緩存(disk),使用NSFileManager實現(xiàn)。

不過這么多函數(shù),我們先從哪看起呢?就從給我印象最深的queryDiskCacheForKey看起。主要是因為這個函數(shù)返回的是一個NSOperation。和SDWebImageManager關(guān)系緊密,尤其是和SDWebImageCombinedOperation的cacheOperation,直接就是作為其返回值。

2. queryDiskCacheForKey


之前簡單的介紹了一下queryDiskCacheForKey函數(shù)實現(xiàn)。具體的細(xì)節(jié)并沒有介紹。尤其是對queryDiskCacheForKey中的done block中有關(guān)cache的部分沒有細(xì)說。這里doneBlock先不討論,先討論queryDiskCacheForKey中的cache部分。

最先看到的關(guān)于cache的部分:

// 首先根據(jù)key(一般指的是圖片的url)去內(nèi)存緩存獲取image
UIImage *image = [self imageFromMemoryCacheForKey:key];

這個imageFromMemoryCacheForKey的具體實現(xiàn):

// 簡單的封裝了NSCache的objectForKey方法
- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key {
    return [self.memCache objectForKey:key];
}

這里,很自然就會想到,objectForKey的對應(yīng)方法是setObject:forKey:。所以我們搜一下看看SDWebImage在哪里使用了setObject:forKey:

總共有三處地方:

queryDiskCacheForKey:done:
imageFromDiskCacheForKey:key
storeImage:recalculateFromImage:imageData:forKey:toDisk

2.1 queryDiskCacheForKey:done:

// 獲取到disk上緩存的image
UIImage *diskImage = [self diskImageForKey:key];
// 如果diskImage存在,并且需要使用memory cache
// 就將diskImage緩存到memory cache中
if (diskImage && self.shouldCacheImagesInMemory) {
    // cost 被用來計算緩存中所有對象的代價。當(dāng)內(nèi)存受限或者所有緩存對象的總代價超過了最大允許的值時,緩存會移除其中的一些對象。
    // 通常,精確的 cost 應(yīng)該是對象占用的字節(jié)數(shù)。
    NSUInteger cost = SDCacheCostForImage(diskImage);
    [self.memCache setObject:diskImage forKey:key cost:cost];
}

這里只有SDCacheCostForImage函數(shù)需要細(xì)看一下,該函數(shù)本質(zhì)就是計算diskImage所要占用的字節(jié)數(shù):

// C語言函數(shù)
// FOUNDATION_STATIC_INLINE表示static __inline__,屬于runtime范疇
FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
    // 這里我覺得這樣寫不是很好,如果這樣寫就更直觀了
    // return (height * scale) * (width * scale)
    return image.size.height * image.size.width * image.scale * image.scale;
}

2.2 imageFromDiskCacheForKey:key

用途和上面一樣,就是從disk中獲取的image,還需更新到內(nèi)存緩存中。

2.3 storeImage:recalculateFromImage:imageData:forKey:toDisk

其實也是和上面一樣,都是為了更新到內(nèi)存緩存中:

// 如果可以使用內(nèi)存緩存
if (self.shouldCacheImagesInMemory) {
    NSUInteger cost = SDCacheCostForImage(image);
    [self.memCache setObject:image forKey:key cost:cost];
}

這里比較關(guān)鍵的其實是storeImage這個函數(shù)的使用場景:

  • 在SDWebImageManager中的downloadImageWithURL中,成功下載到圖片downloadedImage后(或者進行了transform)使用該函數(shù)對image進行緩存
    我們來具體分析下這個函數(shù)。函數(shù)中的前兩個if語句比較好理解,也解釋過了。主要集中在if(toDisk)這個語句中,而toDisk為YES表示應(yīng)該是要往disk memory中存儲。

整個語句塊是放在一個ioQueue的dispatch_queue_t中的:

dispatch_async(self.ioQueue, ^{
    // ......
});

這個ioQueue,我們從字面上理解,就是一個磁盤io的dispatch_queue_t。說簡單點,就是每個下載來的圖片,需要進行磁盤io的過程都放在ioQueue中執(zhí)行。

剩下的部分主要做了兩件事:

  • 1.根據(jù)imageData和image生成待存儲的data
  • 2.利用NSFileManager將待存儲的data存儲起來
// 構(gòu)建一個data,用來存儲到disk中,默認(rèn)值為imageData
NSData *data = imageData;
// 如果image存在,但是需要重新計算(recalculate)或者data為空
// 那就要根據(jù)image重新生成新的data
// 不過要是連image也為空的話,那就別存了
if (image && (recalculate || !data)) {
#if TARGET_OS_IPHONE
    // 我們需要判斷image是PNG還是JPEG
    // PNG的圖片很容易檢測出來,因為它們有一個特定的標(biāo)示 (http://www.w3.org/TR/PNG-Structure.html)
    // PNG圖片的前8個字節(jié)不許符合下面這些值(十進制表示)
    // 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中確實有透明信息,就認(rèn)為image為PNG
    BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
                      alphaInfo == kCGImageAlphaNoneSkipFirst ||
                      alphaInfo == kCGImageAlphaNoneSkipLast);
    BOOL imageIsPng = hasAlpha;
    
    // 但是如果我們已經(jīng)有了imageData,我們就可以直接根據(jù)data中前幾個字節(jié)判斷是不是PNG
    if ([imageData length] >= [kPNGSignatureData length]) {
        // ImageDataHasPNGPreffix就是為了判斷imageData前8個字節(jié)是不是符合PNG標(biāo)志
        imageIsPng = ImageDataHasPNGPreffix(imageData);
    }
    // 如果image是PNG格式,就是用UIImagePNGRepresentation將其轉(zhuǎn)化為NSData,否則按照J(rèn)PEG格式轉(zhuǎn)化,并且壓縮質(zhì)量為1,即無壓縮
    if (imageIsPng) {
        data = UIImagePNGRepresentation(image);
    }
    else {
        data = UIImageJPEGRepresentation(image, (CGFloat)1.0);
    }
#else
    // 當(dāng)然,如果不是在iPhone平臺上,就使用下面這個方法。不過不在我們研究范圍之內(nèi)
    data = [NSBitmapImageRep representationOfImageRepsInArray:image.representations usingType: NSJPEGFileType properties:nil];
#endif
}
// 獲取到需要存儲的data后,下面就要用fileManager進行存儲了
if (data) {
    // 首先判斷disk cache的文件路徑是否存在,不存在的話就創(chuàng)建一個
    // disk cache的文件路徑是存儲在_diskCachePath中的
    if (![_fileManager fileExistsAtPath:_diskCachePath]) {
        [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
    }
    
    // 根據(jù)image的key(一般情況下理解為image的url)組合成最終的文件路徑
    // 上面那個生成的文件路徑只是一個文件目錄,就跟/cache/images/img1.png和cache/images/的區(qū)別一樣
    // defaultCachePathForKey后面會詳解
    NSString *cachePathForKey = [self defaultCachePathForKey:key];
    // 這個url可不是網(wǎng)絡(luò)端的url,而是file在系統(tǒng)路徑下的url
    // 比如/foo/bar/baz --------> file:///foo/bar/baz
    NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
    // 根據(jù)存儲的路徑(cachePathForKey)和存儲的數(shù)據(jù)(data)將其存放到iOS的文件系統(tǒng)
    [_fileManager createFileAtPath:cachePathForKey contents:data attributes:nil];
    
    // 如果不使用iCloud進行備份,就使用NSURLIsExcludedFromBackupKey
    if (self.shouldDisableiCloud) {
        [fileURL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:nil];
    }
}

2.3.1 defaultCachePathForKey

// 簡單封裝了cachePathForKey:inPath
- (NSString *)defaultCachePathForKey:(NSString *)key {
    return [self cachePathForKey:key inPath:self.diskCachePath];
}
// cachePathForKey:inPath
- (NSString *)cachePathForKey:(NSString *)key inPath:(NSString *)path {
    // 根據(jù)傳入的key創(chuàng)建最終要存儲時的文件名
    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字節(jié)(128位:md5加密出來就是128bit)的空間
    unsigned char r[CC_MD5_DIGEST_LENGTH];
    // 官方封裝好的加密方法
    // 把str字符串轉(zhuǎn)換成了32位的16進制數(shù)列(這個過程不可逆轉(zhuǎn)) 存儲到了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;
}

2.4 小結(jié)

上面的幾個分析基本上已經(jīng)把內(nèi)存緩存的存儲和磁盤緩存的存儲講了一下。

  • 內(nèi)存緩存的存儲主要就是使用NSCache自帶的setObject:forKey:及其衍生方法
  • 硬盤緩存的存儲主要是使用NSFileManager進行存儲
    已經(jīng)講完存儲了,那么就不得不提及clear緩存。

3. clear緩存


我們簡單看一下clear的方式,發(fā)現(xiàn)以下幾個函數(shù)需要注意:

removeImageForKeyfromDisk:withCompletion: // 異步地將image從緩存(內(nèi)存緩存以及可選的磁盤緩存)中移除
clearMemory // 清楚內(nèi)存緩存上的所有image
clearDisk // 清除磁盤緩存上的所有image
cleanDisk // 清除磁盤緩存上過期的image

3.1 removeImageForKeyfromDisk:withCompletion:

這個函數(shù)其實是removeImageForKey:等一系列函數(shù)的基礎(chǔ),類似sd_setImageWithURL:placeholderImage:options:progress:completed:函數(shù)。

該函數(shù)主要是根據(jù)key來刪除對應(yīng)緩存image:

- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion {
    
    if (key == nil) {
        return;
    }
    // shouldCacheImagesInMemory為YES表示該圖片會緩存到了內(nèi)存
    // 既然緩存到了內(nèi)存,就要先將內(nèi)存緩存中的image移除
    // 使用的是NSCache的removeObjectForKey:
    if (self.shouldCacheImagesInMemory) {
        [self.memCache removeObjectForKey:key];
    }
    // 如果要刪除磁盤緩存中的image
    if (fromDisk) {
        // 有關(guān)io的部分,都要放在ioQueue中
        dispatch_async(self.ioQueue, ^{
            // 磁盤緩存移除使用的是NSFileManager的removeItemAtPath:error
            [_fileManager removeItemAtPath:[self defaultCachePathForKey:key] error:nil];
            // 如果用戶實現(xiàn)了completion了,就在主線程調(diào)用completion()
            if (completion) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    completion();
                });
            }
        });
    } else if (completion){ // 如果用戶實現(xiàn)了completion了,就在主線程調(diào)用completion()
        completion();
    }
    
}

3.2 clearMemory

簡單地調(diào)用NSCache的removeAllObjects。

3.3 clearDisk

封裝了clearDiskOnCompletion:函數(shù):

- (void)clearDisk {
    [self clearDiskOnCompletion:nil];
}

clearDiskOnCompletion:

- (void)clearDiskOnCompletion:(SDWebImageNoParamsBlock)completion
{
    dispatch_async(self.ioQueue, ^{
        // 先將存儲在diskCachePath中緩存全部移除,然后新建一個空的diskCachePath
        [_fileManager removeItemAtPath:self.diskCachePath error:nil];
        [_fileManager createDirectoryAtPath:self.diskCachePath
                withIntermediateDirectories:YES
                                 attributes:nil
                                      error:NULL];
        // 如果實現(xiàn)了completion,就在主線程中調(diào)用
        if (completion) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completion();
            });
        }
    });
}

3.4 cleanDisk

簡單封裝了cleanDiskWithCompletionBlock:

- (void)cleanDisk {
    [self cleanDiskWithCompletionBlock:nil];
}

cleanDiskWithCompletionBlock:

// 實現(xiàn)了一個簡單的緩存清除策略:清除修改時間最早的file
- (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock {
    dispatch_async(self.ioQueue, ^{
        // 這兩個變量主要是為了下面生成NSDirectoryEnumerator準(zhǔn)備的
        // 一個是記錄遍歷的文件目錄,一個是記錄遍歷需要預(yù)先獲取文件的哪些屬性
        NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
        NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];
        // 遞歸地遍歷diskCachePath這個文件夾中的所有目錄,此處不是直接使用diskCachePath,而是使用其生成的NSURL
        // 此處使用includingPropertiesForKeys:resourceKeys,這樣每個file的resourceKeys對應(yīng)的屬性也會在遍歷時預(yù)先獲取到
        // NSDirectoryEnumerationSkipsHiddenFiles表示不遍歷隱藏文件
        NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
                                                   includingPropertiesForKeys:resourceKeys
                                                                      options:NSDirectoryEnumerationSkipsHiddenFiles
                                                                 errorHandler:NULL];
        // 獲取文件的過期時間,SDWebImage中默認(rèn)是一個星期
        // 不過這里雖然稱*expirationDate為過期時間,但是實質(zhì)上并不是這樣。
        // 其實是這樣的,比如在2015/12/12/00:00:00最后一次修改文件,對應(yīng)的過期時間應(yīng)該是
        // 2015/12/19/00:00:00,不過現(xiàn)在時間是2015/12/27/00:00:00,我先將當(dāng)前時間減去1個星期,得到
        // 2015/12/20/00:00:00,這個時間才是我們函數(shù)中的expirationDate。
        // 用這個expirationDate和最后一次修改時間modificationDate比較看誰更晚就行。
        NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.maxCacheAge];
        // 用來存儲對應(yīng)文件的一些屬性,比如文件所需磁盤空間
        NSMutableDictionary *cacheFiles = [NSMutableDictionary dictionary];
        // 記錄當(dāng)前已經(jīng)使用的磁盤緩存大小
        NSUInteger currentCacheSize = 0;
        // 在緩存的目錄開始遍歷文件.  此次遍歷有兩個目的:
        //
        //  1. 移除過期的文件
        //  2. 同時存儲每個文件的屬性(比如該file是否是文件夾、該file所需磁盤大小,修改時間)
        NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init];
        for (NSURL *fileURL in fileEnumerator) {
            NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:NULL];
            // 當(dāng)前掃描的是目錄,就跳過
            if ([resourceValues[NSURLIsDirectoryKey] boolValue]) {
                continue;
            }
            // 移除過期文件
            // 這里判斷過期的方式:對比文件的最后一次修改日期和expirationDate誰更晚,如果expirationDate更晚,就認(rèn)為該文件已經(jīng)過期,具體解釋見上面
            NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];
            if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
                [urlsToDelete addObject:fileURL];
                continue;
            }
            // 計算當(dāng)前已經(jīng)使用的cache大小,
            // 并將對應(yīng)file的屬性存到cacheFiles中
            NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
            currentCacheSize += [totalAllocatedSize unsignedIntegerValue];
            [cacheFiles setObject:resourceValues forKey:fileURL];
        }
        
        for (NSURL *fileURL in urlsToDelete) {
            // 根據(jù)需要移除文件的url來移除對應(yīng)file
            [_fileManager removeItemAtURL:fileURL error:nil];
        }
        // 如果我們當(dāng)前cache的大小已經(jīng)超過了允許配置的緩存大小,那就刪除已經(jīng)緩存的文件。
        // 刪除策略就是,首先刪除修改時間更早的緩存文件
        if (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) {
            // 直接將當(dāng)前cache大小降到允許最大的cache大小的一般
            const NSUInteger desiredCacheSize = self.maxCacheSize / 2;
            // 根據(jù)文件修改時間來給所有緩存文件排序,按照修改時間越早越在前的規(guī)則排序
            NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
                                                            usingComparator:^NSComparisonResult(id obj1, id obj2) {
                                                                return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];
                                                            }];
            // 每次刪除file后,就計算此時的cache的大小
            // 如果此時的cache大小已經(jīng)降到期望的大小了,就停止刪除文件了
            for (NSURL *fileURL in sortedFiles) {
                if ([_fileManager removeItemAtURL:fileURL error:nil]) {
                    // 獲取該文件對應(yīng)的屬性
                    NSDictionary *resourceValues = cacheFiles[fileURL];
        // 根據(jù)resourceValues獲取該文件所需磁盤空間大小
                    NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
        // 計算當(dāng)前cache大小
                    currentCacheSize -= [totalAllocatedSize unsignedIntegerValue];
                    if (currentCacheSize < desiredCacheSize) {
                        break;
                    }
                }
            }
        }
        // 如果有completionBlock,就在主線程中調(diào)用
        if (completionBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completionBlock();
            });
        }
    });
}

4. init方法

講完了一些具體的緩存方法。反過來,我們研究下SDImageCache的初始化。因為之前的很多方法中的參數(shù)都是已經(jīng)在init中設(shè)置好了。另外一個原因是SDImageCache使用了單例模式。所以相對來說,init方法還是很重要的。

我們先從單例模式看起,正好學(xué)習(xí)下單例模式的正確寫法:

// SDImageCache使用的是單例模式
+ (SDImageCache *)sharedImageCache {
    static dispatch_once_t once;
    static id instance;
    dispatch_once(&once, ^{
        // new = alloc + init
        instance = [self new];
    });
    return instance;
}

4.1 initWithNamespace:

接著我們來看看init方法,原來封裝了initWithNamespace:方法,并且namespace的名稱為@"default"。

- (id)initWithNamespace:(NSString *)ns {
    // iOS使用的是沙盒機制,此處makeDiskCachePath就是獲取Cache目錄,并在Cache目錄下創(chuàng)建default目錄
    // 比如我的mac上就顯示/Users/poloby/Library/Developer/CoreSimulator/Devices/4404872F-4DDD-4AEA-AAD3-71BA1931D4C1/data/Containers/Data/Application/9C7E5D14-FBF0-41F1-A533-E8ACC59FCBAC/Library/Caches/default
    // 后面詳解
    NSString *path = [self makeDiskCachePath:ns];
    // 最終的初始化,后面詳解
return [self initWithNamespace:ns diskCacheDirectory:path];
}

4.1.1 makeDiskCachePath:

- (NSString *)makeDiskCachePath:(NSString*)fullNamespace{
    // 獲取當(dāng)前用戶應(yīng)用下的Caches目錄
    // 返回了一個包含用戶Caches目錄作為第一元素的數(shù)組,所以底下用的是paths[0]
    // 即/Users/poloby/Library/Developer/CoreSimulator/Devices/4404872F-4DDD-4AEA-AAD3-71BA1931D4C1/data/Containers/Data/Application/9C7E5D14-FBF0-41F1-A533-E8ACC59FCBAC/Library/Caches/
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
   // 在Caches目錄下構(gòu)建一個fullNamespace目錄,此處默認(rèn)是default目錄
    return [paths[0] stringByAppendingPathComponent:fullNamespace];
}

4.1.2 initWithNamespace:diskCacheDirectory:

- (id)initWithNamespace:(NSString *)ns diskCacheDirectory:(NSString *)directory {
    if ((self = [super init])) {
        // 再給Caches/default/后面加上fullNamspace
        // 最終可能獲得的diskCachePath可能為
        NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns];
        // 初始化kPNGSignatureData為PNG前8字節(jié)的標(biāo)志:{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}
        // 用于ImageDataHasPNGPreffix這個C函數(shù)中,判斷該data是不是PNG格式
        kPNGSignatureData = [NSData dataWithBytes:kPNGSignatureBytes length:8];
        // 創(chuàng)建名為com.hackemist.SDWebImageCache的IO的串行隊列
        _ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);
        // cache存儲的最長時間為60 * 60 * 24 * 7,即一個星期
        _maxCacheAge = kDefaultCacheMaxCacheAge;
        // 注意此處不是直接使用[[NSCache alloc] init]進行初始化的,而是使用了一個AutoPurgeCache
        // AutoPurgeCache和NSCache不同之處在于,如果AutoPurgeCache收到一個內(nèi)存警告,就會自動釋放內(nèi)存,調(diào)用NSCache的removeAllObjects
        _memCache = [[AutoPurgeCache alloc] init];
        _memCache.name = fullNamespace;
        // 初始化disk cache,一般情況下directory,除非你把Caches刪除了
        if (directory != nil) {
            // 最終結(jié)果是/Users/poloby/Library/Developer/CoreSimulator/Devices/4404872F-4DDD-4AEA-AAD3-71BA1931D4C1/data/Containers/Data/Application/9C7E5D14-FBF0-41F1-A533-E8ACC59FCBAC/Library/Caches/default/com.hackemist.SDWebImageCache.default
            _diskCachePath = [directory stringByAppendingPathComponent:fullNamespace];
        } else {
            // 如果沒有找到Caches目錄,或者新建default目錄失敗。就重新使用makeCachePath新建一個緩存目錄
            NSString *path = [self makeDiskCachePath:ns];
            _diskCachePath = path;
        }
        // 默認(rèn)需要解壓縮圖片
        _shouldDecompressImages = YES;
       // 新建一個NSFileManager也是放在ioQueue中的
        dispatch_sync(_ioQueue, ^{
            _fileManager = [NSFileManager new];
        });
#if TARGET_OS_IPHONE
        // 訂閱了app可能發(fā)生的時間
        // 出現(xiàn)內(nèi)存警告(UIApplicationDidReceiveMemoryWarningNotification),調(diào)用clearMemory
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(clearMemory)
                                                     name:UIApplicationDidReceiveMemoryWarningNotification
                                                   object:nil];
        // 程序終止(UIApplicationWillTerminateNotification),調(diào)用cleanDisk
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(cleanDisk)
                                                     name:UIApplicationWillTerminateNotification
                                                   object:nil];
        // 程序進入后臺運行(UIApplicationDidEnterBackgroundNotification),調(diào)用backgroundCleanDisk
        // backgroundCleanDisk就不贅述了,其實現(xiàn)了在后臺注冊了cleanDiskWithCompletionBlock函數(shù)來處理后臺的磁盤緩存
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(backgroundCleanDisk)
                                                     name:UIApplicationDidEnterBackgroundNotification
                                                   object:nil];
#endif
    }
    return self;
}

5. 總結(jié)

SDImageCache部分有些地方處理還是很簡單的,比如清除緩存策略。如果有大牛實現(xiàn)LRU策略就更好了。

SDWebImage源碼解析到此為止,當(dāng)然還有一些模塊沒有解析,比如MKAnnotationView+WebCache.h、UIButton+WebCache.h、UIImageView+HighlightedWebCache.h以及一些模塊的某些函數(shù)也沒細(xì)講。不過相信大家舉一反三的能力還是很強的。

6. 參考文章


本文轉(zhuǎn)載polobymulberry-博客園

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容