天天看點

自定義SDWebImage圖檔緩沖區自清理機制

SDWebImage是一個開源的第三方庫,使用AFNetworking內建的UIImageView+AFNetworking.h,它對于圖檔的緩存實際應用的是NSURLCache自帶的cache機制。NSURLCache每次都要把緩存的raw data 再轉化為UIImage。

對它稍微改變可以進行三種緩存圖檔清理方式:

1.逾時圖檔緩沖區的清理機制。它本身隻包含預設七天逾時的圖檔緩存機制,這個逾時時間可以修改。

2.圖檔緩沖區大小清理機制,它預設沒有啟用,這個最大緩沖區大小可以修改。

3.圖檔緩沖區手動清理,它預設不具備,可以增加這個功能。

注意:隻有你使用了它加載過圖像這個圖檔緩沖區清除機制才生效。應用啟動後沒有用它加載過圖檔,它就沒有被執行個體化是以也不會自動清理。

它提供了UIImageView的一個分類,以支援從遠端伺服器下載下傳并緩存圖檔的功能

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

• 一個異步的圖檔加載器

• 一個異步的記憶體+磁盤圖檔緩存,并具有自動緩存過期處理功能

• 支援GIF圖檔

• 支援WebP圖檔

• 背景圖檔解壓縮處理

• 確定同一個URL的圖檔不被下載下傳多次

• 確定虛假的URL不會被反複加載

• 確定下載下傳及緩存時,主線程不被阻塞

• 優良的性能

• 使用GCD和ARC

• 支援Arm64

app事件注冊使用經典的觀察者模式,當觀察到記憶體警告、程式被終止、程式進入背景這些事件時,程式将自動調用相應的方法處理。

可以看到最常用的緩存檔案被清理的時機時程式進入背景,一般的應用不會不進入背景就出現記憶體不足了吧!絕大部分使用者都是過一段時間會把應用切換到背景或殺掉,若是讓應用切換到前台你的手機電量也吃不消。一般的應用也不會在七天内下載下傳很大容量的圖檔。當然當你的手機已經産生了記憶體告警也能産生緩存圖檔清除。是以它的這種清除緩存圖檔的時機足夠滿足你的緩存圖檔處理。

注意:它預設隻支援超過7天的圖檔清除。不對圖檔緩存大小進行控制。當然它已經做了這種機制,隻是maxCacheSize為預設值0,是以不生效。

當然若你的應用下載下傳的都是不壓縮的大檔案,如醫學圖像檔案。那麼你就要對圖檔緩沖區進行控制了,你可以修改下這個庫。若你的應用有可能出現在亮螢幕的情況下就下載下傳大量圖檔,并且圖檔整體很大,那麼你也可以增加你清除圖檔的時機,主動調用圖檔緩存清理。當然你覺得7天時間太長需要修改逾時時間,那麼你也可以修改下這個庫。最好的修改方法是對SDImageCache生成一個子類,來設定緩沖區的大小,清空圖檔緩沖區,當然你要把該類的對象存起來了,比直接修改這個庫稍微麻煩下。

若你想直接修改第三方庫來實作對緩沖區圖檔檔案總大小控制或過期時間修改,隻需要修改SDImageCache.m檔案的3行代碼就可以。若你想主動清理圖檔緩沖區,稍微修改下這個第三方庫就可以。代碼注釋已經明确說明了:

static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week
//若想對圖檔緩沖區大小進行控制,請打開這個一行注釋和後面用到該變量的地方的注釋。這個是非本庫的内容,若用pods重新下載下傳後,若想使用該功能請加入相關的兩行代碼
//static const NSInteger kDefaultCacheMaxCacheSize = 100 * 1024 * 1024;//100 * 1024 * 1024; // 100 MB

- (id)initWithNamespace:(NSString *)ns diskCacheDirectory:(NSString *)directory {
    if ((self = [super init])) {
        NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns];

        // 初始化PNG标記資料
        kPNGSignatureData = [NSData dataWithBytes:kPNGSignatureBytes length:8];

        // 建立ioQueue串行隊列負責對硬碟的讀寫
        _ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);

        // 初始化預設的最大緩存時間
        _maxCacheAge = kDefaultCacheMaxCacheAge;

//        //若想對圖檔緩沖區大小進行控制,請打開這個一行注釋和前面用到該變量聲明的地方的注釋。這個是非本庫的内容,若用pods重新下載下傳後,若想使用該功能請加入相關的兩行代碼
//        _maxCacheSize = kDefaultCacheMaxCacheSize;

        // 初始化記憶體緩存,詳見接下來解析的記憶體緩存類
        _memCache = [[AutoPurgeCache alloc] init];
        _memCache.name = fullNamespace;

        // 初始化磁盤緩存
        if (directory != nil) {
            _diskCachePath = [directory stringByAppendingPathComponent:fullNamespace];
        } else {
            NSString *path = [self makeDiskCachePath:ns];
            _diskCachePath = path;
        }

        // 設定預設解壓縮圖檔
        _shouldDecompressImages = YES;

        // 設定預設開啟記憶體緩存
        _shouldCacheImagesInMemory = YES;

        // 設定預設不使用iCloud
        _shouldDisableiCloud = YES;

        dispatch_sync(_ioQueue, ^{
            _fileManager = [NSFileManager new];
        });

#if TARGET_OS_IOS
        // app事件注冊,記憶體警告事件,程式被終止事件,已經進入背景模式事件,詳見後文的解析:app事件注冊。
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(clearMemory)
                                                     name:UIApplicationDidReceiveMemoryWarningNotification
                                                   object:nil];

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(cleanDisk)
                                                     name:UIApplicationWillTerminateNotification
                                                   object:nil];

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(backgroundCleanDisk)
                                                     name:UIApplicationDidEnterBackgroundNotification
                                                   object:nil];
        //若想使用者主動清理圖檔緩沖區,請打開這個注釋。這個是非本庫的内容,若用pods重新下載下傳後,若想使用該功能請加入該代碼,保證代碼覆寫後的重新加入
//        [[NSNotificationCenter defaultCenter] addObserver:self
//                                                 selector:@selector(cleanDisk)
//                                                     name:@"UserClenDiskNotification"
//                                                   object:nil]; #endif
    }

    return self;
}      
[[NSNotificationCenter defaultCenter] postNotificationName:@"UserClenDiskNotification";      
- (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock {
    dispatch_async(self.ioQueue, ^{
        NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
        NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];

        // 使用目錄枚舉器擷取緩存檔案的三個重要屬性:(1)URL是否為目錄;(2)内容最後更新日期;(3)檔案總的配置設定大小。
        NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
                                                   includingPropertiesForKeys:resourceKeys
                                                                      options:NSDirectoryEnumerationSkipsHiddenFiles
                                                                 errorHandler:NULL];
       // 計算過期日期,預設為一星期前的緩存檔案認為是過期的。
        NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.maxCacheAge];
        NSMutableDictionary *cacheFiles = [NSMutableDictionary dictionary];
        NSUInteger currentCacheSize = 0;

        // 枚舉緩存目錄的所有檔案,此循環有兩個目的:
        //
        //  1. 清除超過過期日期的檔案。
        //  2. 為以大小為基礎的第二輪清除儲存檔案屬性。        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;
            }

            // 儲存保留下來的檔案的引用并計算檔案總的大小。
            NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
            currentCacheSize += [totalAllocatedSize unsignedIntegerValue];
            [cacheFiles setObject:resourceValues forKey:fileURL];
        }
        //清除記錄的過期緩存檔案
        for (NSURL *fileURL in urlsToDelete) {
            [_fileManager removeItemAtURL:fileURL error:nil];
        }

        // 如果我們保留下來的磁盤緩存檔案仍然超過了配置的最大大小,那麼進行第二輪以大小為基礎的清除。我們首先删除最老的檔案。前提是我們設定了最大緩存
        if (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) {
            // 此輪清除的目标是最大緩存的一半。
            const NSUInteger desiredCacheSize = self.maxCacheSize / 2;

            // 用它們最後更新時間排序保留下來的緩存檔案(最老的最先被清除)。
            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();
            });
        }
    });
}      

繼續閱讀