SDWebImage源碼解析(二)——SDImageCache緩存模塊

第二篇的寫在前面

本系列的上一篇文章已經從整個SDWebImage的框架和流程圖入手介紹了WebCache+WebManager模塊。在發布了這個系列的第一篇文章之后,我也去參考了一下相關的同樣介紹SDWebImage框架的的文章,就是希望自己的解析能夠更準確一些。同樣有的文章把源碼中所有英文注釋都去掉替換成了自己翻譯+解釋的中文注釋,但是我覺得如果有一定英文閱讀能力,直接給出源碼中對相關語義的解釋可能更為直接一些,也防止了博客作者一定程度上的曲解。
在本系列末尾會列出參考文章。
好了,廢話不多說,直接進入正題。

從上一篇繼續

上一篇的大部分篇幅,使用了源碼+注釋的方式介紹了SDWebImageManager模塊下的loadImageWithURL()方法,該方法用于通過調用者傳入的URL從網絡/緩存獲取圖片。其中有一個重要的方法:

//請求緩存
    operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
           //do something.?
}];

這個方法是屬于本篇文章主角——SDImageCache類內部的方法。

SDImageCache + SDImageCacheConfig

SDWebImage的緩存模塊有兩個類:SDImageCache和一個輔助類SDImageCacheConfigSDImageCacheConfig 用于設置與緩存相關的一些屬性,與上文一樣,在文章中如果有涉及到會單獨將這個屬性拿出來作解釋。

SDWebImageManager類似,SDImageCache同樣被設計為一個單例類,內部提供了一個全能初始化(designated initializer)方法:

/**
 * Init a new cache store with a specific namespace and directory
 *
 * @param ns        The namespace to use for this cache store
 * @param directory Directory to cache disk images in
 */

- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
                       diskCacheDirectory:(nonnull NSString *)directory {
    if ((self = [super init])) {
        NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns];
        
        // 初始化了一個串行的隊列賦值給自身成員變量ioQueue
        //后面介紹的代碼會使用到這個串行隊列
        _ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);
        //初始化成員變量config
        _config = [[SDImageCacheConfig alloc] init];
        
        // Init the memory cache 初始化內存緩存 使用NSCache類實現
        /*AutoPurgeCache 是 繼承于NSCache的一個類 里面封裝了對系統    
        UIApplicationDidReceiveMemoryWarningNotification
         通知的監聽,當收到該通知時,移除內部所有對象
        */
        _memCache = [[AutoPurgeCache alloc] init];
        _memCache.name = fullNamespace;

        //初始化磁盤緩存
        //directory是傳入的磁盤緩存將要存放的路徑
        if (directory != nil) {// 傳入的directory參數不為nil
            _diskCachePath = [directory stringByAppendingPathComponent:fullNamespace];
        } else {// 傳入的directory == nil
            NSString *path = [self makeDiskCachePath:ns];
            /* 
             makeDiskCachePath: 創建目標文件路徑
             NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
             return [paths[0] stringByAppendingPathComponent:fullNamespace];
             */
            _diskCachePath = path;
        }
        //在ioQueue中初始化成員變量fileManager
        dispatch_sync(_ioQueue, ^{
            _fileManager = [NSFileManager new];
        });

#if SD_UIKIT
        // Subscribe to app events
        //監聽系統通知
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(clearMemory)
                                                     name:UIApplicationDidReceiveMemoryWarningNotification
                                                   object:nil];

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

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(backgroundDeleteOldFiles)
                                                     name:UIApplicationDidEnterBackgroundNotification
                                                   object:nil];
#endif
    }

    return self;
}

請求緩存

如果說我們要自己設計一個圖片緩存模塊,那么最基本最核心的功能自然是:

  1. 從緩存中獲取圖片。
  2. 將圖片緩存。
  3. 緩存管理機制

同樣的,SDWebImage在設計圖片緩存模塊的時候也遵循著這個思路。

請求緩存queryCacheOperation方法

上文提及的在loadImageWithURL()方法中,manager通過調用queryCacheOperation方法請求緩存。下面給出這部分代碼:

/*
 typedef void(^SDCacheQueryCompletedBlock)(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType);
 */
/*通過傳入的key從緩存中查找圖片,通過回調block的方式返回給調用者*/
/*完成回調SDCacheQueryCompletedBlock的定義在上面給出*/
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
    if (!key) {
        //如果key不存在,執行回調,返回
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }

    //首先根據key檢查in-memory的緩存中有沒有圖片
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    if (image) {//在內存中獲取到了圖片
        NSData *diskData = nil;
        if ([image isGIF]) {
             //在所有keyPaths中根據key使用[NSData dataWithContentsOfFile]方法獲取data
            diskData = [self diskImageDataBySearchingAllPathsForKey:key];
        }
        if (doneBlock) {
            //執行回調將image 和 data傳出, 然后返回
            doneBlock(image, diskData, SDImageCacheTypeMemory);
        }
        return nil;
    }
    /*內存中未查找到緩存圖片,繼續從磁盤緩存中查找*/
    //創建一個新的operation 由于下面的緩存查找操作會在子線程中異步執行
    //所以這里直接返回該operation給manager
    NSOperation *operation = [NSOperation new];
    
    dispatch_async(self.ioQueue, ^{
        //在ioQueue 執行緩存查找操作 不阻塞主線程
        if (operation.isCancelled) {
            // do not call the completion if cancelled
            return;
        }
        //生成一個新的autoreleasepool
        //獲取磁盤緩存
        @autoreleasepool {
            //在所有keyPaths中根據key使用[NSData dataWithContentsOfFile]方法獲取data
            NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
            //根據key獲取image
            UIImage *diskImage = [self diskImageForKey:key];
            if (diskImage && self.config.shouldCacheImagesInMemory) {
                //在磁盤緩存中獲取到image
                //訪問config的shouldCacheImagesInMemory屬性判斷是否需要將圖片緩存到內存中
                //計算空間花銷 返回值為該圖片的像素點個數
                NSUInteger cost = SDCacheCostForImage(diskImage);
               //將圖片緩存到內存中
                [self.memCache setObject:diskImage forKey:key cost:cost];
            }

            if (doneBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    //主線程執行回調
                    doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
                });
            }
        }
    });

    return operation;
}

在這個方法中,有幾個方法的實現在下面會具體介紹:

diskImageForKey方法

這個方法通過訪問磁盤緩存獲取圖片。

//在磁盤緩存中根據key查找圖片
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key {
    //1.獲取data
    NSData *data = [self diskImageDataBySearchingAllPathsForKey:key];
    if (data) {
        //2. 根據data 獲取 image
        UIImage *image = [UIImage sd_imageWithData:data];
        
        //3. 返回大小比例縮放正確的圖片
        image = [self scaledImageForKey:key image:image];
        
        if (self.config.shouldDecompressImages) {
            //如果圖片需要解壓 使用SDWebImageDecoder進行解碼
            /*
             Decompressing images that are downloaded and cached can improve performance but can consume lot of memory.
             默認為YES
             */
            image = [UIImage decodedImageWithImage:image];
        }
        return image;
    }
    else {
        return nil;
    }
}

在第三步中,image獲取到一個縮放過后的圖片。通常關心這個函數到底以一個怎樣的系數或者機制來縮放。[self scaledImageForKey:key image:image];調用了下面的C函數。

//C 內聯函數
inline UIImage *SDScaledImageForKey(NSString * _Nullable key, UIImage * _Nullable image) {
    if (!image) {
        return nil;
    }
   if ((image.images).count > 0) {
        //對于animated image進行處理
        //每一幀的圖片都要進行scale操作
        NSMutableArray<UIImage *> *scaledImages = [NSMutableArray array];

        for (UIImage *tempImage in image.images) {
            
            [scaledImages addObject:SDScaledImageForKey(key, tempImage)];
        }
        return [UIImage animatedImageWithImages:scaledImages duration:image.duration];
    }
    else {
        if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {
            CGFloat scale = 1;
            //獲取縮放比例
            if (key.length >= 8) {
                NSRange range = [key rangeOfString:@"@2x."];
                if (range.location != NSNotFound) {
                    scale = 2.0;
                }
            
                range = [key rangeOfString:@"@3x."];
                if (range.location != NSNotFound) {
                    scale = 3.0;
                }
            }
            //生成縮放后的圖片 然后返回
            UIImage *scaledImage = [[UIImage alloc] initWithCGImage:image.CGImage scale:scale orientation:image.imageOrientation];
            image = scaledImage;
        }
        return image;
    }
}

根據以上代碼,根據傳入的key中的關鍵信息來對源圖片進行比例放大以獲取對應大小的圖片。

SDCacheCostForImage計算空間花銷

NSCache提供一套類似于字典的key/value方式來進行存取內部對象。

- (nullable ObjectType)objectForKey:(KeyType)key;
- (void)setObject:(ObjectType)obj forKey:(KeyType)key; // 0 cost
- (void)setObject:(ObjectType)obj forKey:(KeyType)key cost:(NSUInteger)g;

@property NSUInteger totalCostLimit;    // limits are imprecise/not strict
@property NSUInteger countLimit;    // limits are imprecise/not strict

使用方法類似 NSDictionary。可以通過設置 NSCache能占用的最大空間花銷totalCostLimit或者最大對象緩存數量countLimit。比如我們設置緩存最多占用20mb,然后每次存入緩存圖片時將圖片大小作為cost參數傳入,當緩存大小或數量超過限定值時,內部的緩存機制就會自動為我們執行清理操作而且NSCache是線程安全的。
同樣的,在SDImageCache中提供了兩個與此對應的屬性用于管理NSCache的這個特性。

/**
 * The maximum "total cost" of the in-memory image cache. The cost function is the number of pixels held in memory.
 */
@property (assign, nonatomic) NSUInteger maxMemoryCost;

/**
 * The maximum number of objects the cache should hold.
 */
@property (assign, nonatomic) NSUInteger maxMemoryCountLimit;

但是需要注意的是:在SDWebImage內部,并沒有任何代碼顯式的為內存緩存AutoPurgeCache(前面已經提過,這是繼承于NSCache的一個子類)設置最大空間花銷和最大緩存對象數量,除非使用者(我們)為這個類的以上兩個屬性賦值。

前面舉的例子中,在setObject方法中傳入cost的是該對象所占用的內存大小,即字節數的多少。但是是否在本框架中,也使用同樣的計算機制呢。SDImageCache類中,調用下面的C函數進行計算cost參數。

//為圖片計算空間花銷 以像素點多少為單位
//FOUNDATION_STATIC_INLINE 為 系統定義的宏 (== static inline) 內聯函數定義
//C函數
FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
    return image.size.height * image.size.width * image.scale * image.scale;
}

因此我們可以看到,SDImageCache中計算占用內存大小的方法并不是單純使用字節數為單位,而是以像素點的個數為單位進行計算。這一點在一些博客中并沒有提及或者錯誤的說明了。

流程圖總結

接下來用一個流程圖總結一下本小節內容。


SDImageCache請求緩存.png

圖片緩存

在了解緩存獲取的設計之后,圖片緩存模塊的設計與其相近。話不多說,直接上代碼。

storeImage方法實現

/**
 * Asynchronously store an image into memory and disk cache at the given key.
 *
 * @param image           The image to store
 * @param imageData       The image data as returned by the server, this representation will be used for disk storage
 *                        instead of converting the given image object into a storable/compressed image format in order
 *                        to save quality and CPU
 * @param key             The unique image cache key, usually it's image absolute URL
 * @param toDisk          Store the image to disk cache if YES
 * @param completionBlock A block executed after the operation is finished
 */
- (void)storeImage:(nullable UIImage *)image
         imageData:(nullable NSData *)imageData
            forKey:(nullable NSString *)key
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock {
    if (!image || !key) {
        //image和key都為nil 執行block 返回
        if (completionBlock) {
            completionBlock();
        }
        return;
    }
    // if memory cache is enabled
    //1. 如果設置了需要進行memory cache 將圖片緩存到內存
    
    if (self.config.shouldCacheImagesInMemory) {
        //1.1 計算空間開銷
        NSUInteger cost = SDCacheCostForImage(image);
        //1.2 緩存到NSCache中
        [self.memCache setObject:image forKey:key cost:cost];
    }
    //2. 如果需要緩存到磁盤中
    if (toDisk) {
        // 在ioQueue 異步緩存
        dispatch_async(self.ioQueue, ^{
            @autoreleasepool {
                NSData *data = imageData;
                if (!data && image) {
                    //data為空 則需要計算data
                    //2.1 根據data獲取SDImageFormat 這是一個 枚舉類型
                    SDImageFormat imageFormatFromData = [NSData sd_imageFormatForImageData:data];
                    //2.2 根據圖片格式生成data
                    data = [image sd_imageDataAsFormat:imageFormatFromData];
                }
                //2.3 將圖片數據存儲到磁盤中,以key為索引
                [self storeImageDataToDisk:data forKey:key];
            }
            //3.主線程執行回調
            if (completionBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    completionBlock();
                });
            }
        });
    } else {//3.不需要進行磁盤緩存,直接執行回調
        if (completionBlock) {
            completionBlock();
        }
    }
}

緩存的基本過程已經在注釋寫清楚。核心方法是調用:

- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key;

進行磁盤緩存。

storeImageDataToDisk方法

storeImageDataToDisk方法將key(通常是URL)和上面方法生成的data緩存到磁盤。

/**
 * Synchronously store image NSData into disk cache at the given key.
 *
 * @warning This method is synchronous, make sure to call it from the ioQueue
 *
 * @param imageData  The image data to store
 * @param key        The unique image cache key, usually it's image absolute URL
 */

- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
    if (!imageData || !key) {
        return;
    }
    //確保該方法在ioQueue中同步地執行
    [self checkIfQueueIsIOQueue];
    //判斷緩存文件路徑是否存在,如果不存在則使用fileManager新建
    if (![_fileManager fileExistsAtPath:_diskCachePath]) {
        [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
    }
    
    // get cache Path for image key
    NSString *cachePathForKey = [self defaultCachePathForKey:key];
    // transform to NSUrl
    NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
    
    //在當前文件夾下創建文件
    [_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil];
    
    // disable iCloud backup
    if (self.config.shouldDisableiCloud) {
        [fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
    }
}

這個方法需要在串行隊列ioQueue中同步執行,主要任務就是新建存儲圖片數據的文件夾,并使用[_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil]imageData寫入該路徑下。

補充:緩存文件名

磁盤緩存使用了傳入的key的MD5轉換之后的結果作為該圖片的磁盤緩存文件名。

- (nullable NSString *)cachedFileNameForKey:(nullable NSString *)key {
    const char *str = key.UTF8String;
    if (str == NULL) {
        str = "";
    }
    //開辟一個16字節的空間 
    //#define CC_MD5_DIGEST_LENGTH    16          /* digest length in bytes */
    unsigned char r[CC_MD5_DIGEST_LENGTH];
    //執行加密
    CC_MD5(str, (CC_LONG)strlen(str), r);
    //轉換為字符串 x% 為16進制
    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;
}

緩存清理

在SDWebImage框架下,緩存清理情況分為兩種:

  1. Cache clear 即清除所有緩存。
  2. Delete old files 即整理緩存空間。

第一種情況比較簡單,直接刪除所有緩存數據即可。

#pragma mark - Cache clean Ops
//1. 清理內存
- (void)clearMemory {
    [self.memCache removeAllObjects];
}
//2. 清理磁盤
- (void)clearDiskOnCompletion:(nullable SDWebImageNoParamsBlock)completion {
    dispatch_async(self.ioQueue, ^{
        //2.1 刪除緩存目錄下所有文件
        [_fileManager removeItemAtPath:self.diskCachePath error:nil];
       //2.2 新建一個同名文件夾
        [_fileManager createDirectoryAtPath:self.diskCachePath
                withIntermediateDirectories:YES
                                 attributes:nil
                                      error:NULL];

        if (completion) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completion();
            });
        }
    });
}

第二種情況用于整理磁盤緩存文件。

- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock {
    dispatch_async(self.ioQueue, ^{
        NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
        /**
         這里給出key對應的信息
         NSURLContentModificationDateKey-> The time the resource content was last modified (Read-write, value type NSDate)
         NSURLIsDirectoryKey -> True for directories (Read-only, value type boolean NSNumber)
         NSURLTotalFileAllocatedSizeKey -> Total allocated size of the file in bytes (this may include space used by metadata), or nil if not available. (Read-only, value type NSNumber)
         */
        NSArray<NSString *> *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];
        //初始化enumerator,在后續會遍歷diskCachePath目錄下的文件,通過resoureceKeys獲取相關屬性
        NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
                                                   includingPropertiesForKeys:resourceKeys
                                                                      options:NSDirectoryEnumerationSkipsHiddenFiles
                                                                 errorHandler:NULL];
        //獲取config類中調用者設置的最大緩存壽命
        NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.config.maxCacheAge];
        NSMutableDictionary<NSURL *, NSDictionary<NSString *, id> *> *cacheFiles = [NSMutableDictionary dictionary];
        NSUInteger currentCacheSize = 0;

        //遍歷cache directory下所有文件,并進行以下操作:
        //1. 移除所有生成日期早于expirationDate的文件
        //2. 保存文件大小相關的屬性,用于基于文件大小的緩存清理操作
        
        //初始化一個數組保存要移除的文件url
        NSMutableArray<NSURL *> *urlsToDelete = [[NSMutableArray alloc] init];
        for (NSURL *fileURL in fileEnumerator) {
            NSError *error;
            //1. 提取出與resourceKeys對應的值
            NSDictionary<NSString *, id> *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:&error];

            //2. 跳過目錄和錯誤情況
            if (error || !resourceValues || [resourceValues[NSURLIsDirectoryKey] boolValue]) {
                continue;
            }

            //3. 記錄所有生成日期早于expirationDate的文件
            NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];
            if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
                [urlsToDelete addObject:fileURL];
                continue;
            }
            //4. 記錄文件相關屬性 保存在cacheFiles字典中 url -> key/value (string -> id)
            //currentCacheSize記錄當前目錄下所有緩存文件的大小
            NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
            currentCacheSize += totalAllocatedSize.unsignedIntegerValue;
            cacheFiles[fileURL] = resourceValues;
        }
        //5. 清除第三步記錄的過期文件
        for (NSURL *fileURL in urlsToDelete) {
            [_fileManager removeItemAtURL:fileURL error:nil];
        }
        // 6. 如果currentCacheSize > config中配置的最大文件大小
        // 執行第二步清理操作,首先清理最早被緩存的文件
        if (self.config.maxCacheSize > 0 && currentCacheSize > self.config.maxCacheSize) {
            // Target half of our maximum cache size for this cleanup pass.
            // 6.1 清理的目標為最大緩存大小的一半
            const NSUInteger desiredCacheSize = self.config.maxCacheSize / 2;

            // 6.2 按文件的最后修改時間進行排序(舊文件在前)
            NSArray<NSURL *> *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
                                                                     usingComparator:^NSComparisonResult(id obj1, id obj2) {
                                                                         return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];
                                                                     }];

            // 6.3 按排序數組從前往后刪除文件 直到清理目標大小
            for (NSURL *fileURL in sortedFiles) {
                if ([_fileManager removeItemAtURL:fileURL error:nil]) {
                    NSDictionary<NSString *, id> *resourceValues = cacheFiles[fileURL];
                    NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
                    currentCacheSize -= totalAllocatedSize.unsignedIntegerValue;

                    if (currentCacheSize < desiredCacheSize) {
                        //達到目標 終止循環
                        break;
                    }
                }
            }
        }
        // 7. 清理完成 主線程執行回調
        if (completionBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completionBlock();
            });
        }
    });
}

SDImageCache首先清理過期文件。如果設置了最大緩存空間config.maxCacheSize,且清理完過期文件后發現占用的磁盤大小仍大于self.config.maxCacheSize,則對文件按照其修改日期的先后進行排序,舊文件排在前面。最后從排序數組中根據其URL一個一個從磁盤中移除,直到

currentCacheSize < 0.5 * maxCacheSize;

緩存清理的時機

這個時候我們再回頭看SDImageCache的全能初始化方法中注冊通知監聽系統通知的代碼。

       [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(clearMemory)
                                                     name:UIApplicationDidReceiveMemoryWarningNotification
                                                   object:nil];

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

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(backgroundDeleteOldFiles)
                                                     name:UIApplicationDidEnterBackgroundNotification
                                                   object:nil];

緩存清理的時機有如下幾個:

  1. 接收到UIApplicationDidReceiveMemoryWarningNotification 內存警告通知時,清除所有內存緩存。
  2. 接收到name:UIApplicationWillTerminateNotification 應用即將被關閉通知時,整理磁盤緩存。
  3. 接收到name:UIApplicationWillTerminateNotification 應用即將進入后臺通知時,在后臺整理磁盤緩存。

總結

SDWebImage的緩存模塊本文章大致總結到這里,主要的功能和函數都給出。篇幅較長,也說明了本模塊的重要性,同樣是找工作面試常常會問到的地方。盡管如此,本模塊的核心邏輯非常簡單:先內存后磁盤(如有沒有額外設置的情況下)。無論是獲取緩存圖片還是將圖片緩存。
與緩存類SDImageCache配合使用的還有SDImageCacheConfig類,用于配置與緩存的相關信息,例如最大緩存數量等。
下一篇將對SDWebImage的圖片解碼器進行解析。

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

推薦閱讀更多精彩內容