每日一問(wèn)26——SDWebImage(緩存)

前言

使用SDWebImage為我們帶來(lái)的另一個(gè)方便就是它提供圖片的緩存功能桥爽,自動(dòng)將下載好的圖片緩存到本地朱灿,防止重復(fù)下載。本篇文章主要學(xué)習(xí)的就是SDWebImage的緩存功能是怎么實(shí)現(xiàn)的钠四。

Cache

提供緩存相關(guān)的文件有2個(gè)盗扒,分別是SDImageCacheConfig,SDImageCache

  • SDImageCacheConfig:提供緩存操作的配置屬性
//是否解壓下載的圖片缀去,默認(rèn)是YES,但是會(huì)消耗掉很多內(nèi)存侣灶,如果遇到內(nèi)存不足的crash時(shí),將值設(shè)為NO
@property (assign, nonatomic) BOOL shouldDecompressImages;

//允許自動(dòng)上傳的iCloud
@property (assign, nonatomic) BOOL shouldDisableiCloud;

//允許緩存到內(nèi)存
@property (assign, nonatomic) BOOL shouldCacheImagesInMemory;

//讀取磁盤緩存的策略
@property (assign, nonatomic) NSDataReadingOptions diskCacheReadingOptions;

//最大緩存時(shí)間
@property (assign, nonatomic) NSInteger maxCacheAge;

//最大緩存大小
@property (assign, nonatomic) NSUInteger maxCacheSize;
  • SDImageCache:實(shí)現(xiàn)主要的緩存邏輯缕碎,緩存分為內(nèi)存緩存memCache磁盤緩存fileManager兩部分褥影,并提供存儲(chǔ),查詢咏雌,讀取凡怎,刪除相關(guān)操作。
內(nèi)存緩存

內(nèi)存緩存是使用NSCache實(shí)現(xiàn)的赊抖,NSCache使用上類似字典统倒,可以用key-Value的方式存取數(shù)據(jù)。但是NSCache底層實(shí)現(xiàn)和NSDictionary不同(NSCache是線程安全的)氛雪。

@interface AutoPurgeCache : NSCache
@end

@implementation AutoPurgeCache

- (nonnull instancetype)init {
    self = [super init];
    if (self) {
#if SD_UIKIT
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
    }
    return self;
}

可以看到檐薯,里面實(shí)現(xiàn)了一個(gè)NSCache的子類。添加了一個(gè)觀察者注暗,當(dāng)收到內(nèi)存警告的時(shí)候坛缕,移除所有的緩存。

磁盤緩存

磁盤緩存使用NSFileManager實(shí)現(xiàn)捆昏,通過(guò)一個(gè)串行隊(duì)列進(jìn)行異步任務(wù)管理赚楚。并要求所有寫操作都必須放在這個(gè)隊(duì)列中執(zhí)行

_ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);

通過(guò)標(biāo)簽檢測(cè)隊(duì)列是不是ioQueue

- (void)checkIfQueueIsIOQueue {
    const char *currentQueueLabel = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
    const char *ioQueueLabel = dispatch_queue_get_label(self.ioQueue);
    if (strcmp(currentQueueLabel, ioQueueLabel) != 0) {
        NSLog(@"This method should be called from the ioQueue");
    }
}

核心緩存方法是- (void)storeImage:(nullable UIImage *)image imageData:(nullable NSData *)imageData forKey:(nullable NSString *)key toDisk:(BOOL)toDisk completion:(nullable SDWebImageNoParamsBlock)completionBlock通過(guò)這個(gè)方法將圖片進(jìn)行內(nèi)存緩存或磁盤緩存。

//檢測(cè)正確性
if (!image || !key) {
        if (completionBlock) {
            completionBlock();
        }
        return;
    }

//如果需要進(jìn)行內(nèi)存緩存骗卜,則將圖片直接緩存進(jìn)NSCache中
    if (self.config.shouldCacheImagesInMemory) {
        NSUInteger cost = SDCacheCostForImage(image);
        [self.memCache setObject:image forKey:key cost:cost];
    }
    
//如果需要磁盤緩存
    if (toDisk) {
//在串行隊(duì)列中異步執(zhí)行緩存操作
        dispatch_async(self.ioQueue, ^{
//大量對(duì)象生成釋放宠页,使用autoreleasepool控制內(nèi)存
            @autoreleasepool {
                NSData *data = imageData;
                if (!data && image) {
                     // 圖片二進(jìn)制數(shù)據(jù)不存在左胞,重新生成,首先獲取圖片的格式举户,然后使用`UIImagePNGRepresentation`或者`UIImageJPEGRepresentation`生成圖片烤宙,之后會(huì)查看NSData的這些分類方法
                    data = [[SDWebImageCodersManager sharedInstance] encodedDataWithImage:image format:SDImageFormatPNG];
                }
// 磁盤緩存核心方法
                [self storeImageDataToDisk:data forKey:key];
            }
            
            if (completionBlock) {
// 主線程回調(diào)
                dispatch_async(dispatch_get_main_queue(), ^{
                    completionBlock();
                });
            }
        });
    } else {
        if (completionBlock) {
            completionBlock();
        }
    }

通過(guò)- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key方法將數(shù)據(jù)寫入進(jìn)磁盤

//正確性校驗(yàn)
if (!imageData || !key) {
        return;
    }
    //驗(yàn)證當(dāng)前隊(duì)列是否正確
    [self checkIfQueueIsIOQueue];
    //檢測(cè)磁盤中是否已存在該文件,如果不存在則創(chuàng)建這個(gè)目錄
    if (![_fileManager fileExistsAtPath:_diskCachePath]) {
        [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
    }
    
    //創(chuàng)建文件名俭嘁,并轉(zhuǎn)化為URL格式
    NSString *cachePathForKey = [self defaultCachePathForKey:key];
    NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
    //存儲(chǔ)圖片到該路徑
    [_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil];
    
    // 如果需要躺枕,上傳到iCloud
    if (self.config.shouldDisableiCloud) {
        [fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
    }
緩存查詢

核心方法- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock,當(dāng)查詢圖片時(shí)供填,該操作會(huì)在內(nèi)存中放置一份緩存拐云,如果確定需要緩存到磁盤,則將磁盤緩存操作作為一個(gè)task放到串行隊(duì)列中處理近她。

//正確性校驗(yàn)
if (!key) {
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }

    // 檢測(cè)內(nèi)存中是否已緩存該圖片
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    if (image) {
        NSData *diskData = nil;
// isGIF單獨(dú)處理
        if (image.images) {
            diskData = [self diskImageDataBySearchingAllPathsForKey:key];
        }
// 查詢完成叉瘩,是內(nèi)存緩存,查詢操作不需要在io隊(duì)列中執(zhí)行
        if (doneBlock) {
            doneBlock(image, diskData, SDImageCacheTypeMemory);
        }
        return nil;
    }
//內(nèi)存上沒(méi)有粘捎,創(chuàng)建一個(gè)任務(wù)
    NSOperation *operation = [NSOperation new];
//在io隊(duì)列執(zhí)行
    dispatch_async(self.ioQueue, ^{
        if (operation.isCancelled) {
            // 檢測(cè)任務(wù)狀態(tài)
            return;
        }

        @autoreleasepool {
//及時(shí)釋放內(nèi)存
            NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
            UIImage *diskImage = [self diskImageForKey:key];
//如果需要緩存到內(nèi)存薇缅,則把讀取出來(lái)的數(shù)據(jù)拷貝一份到內(nèi)存中
            if (diskImage && self.config.shouldCacheImagesInMemory) {
                NSUInteger cost = SDCacheCostForImage(diskImage);
                [self.memCache setObject:diskImage forKey:key cost:cost];
            }

            if (doneBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
//回調(diào)
                    doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
                });
            }
        }
    });
//返回該任務(wù)給調(diào)度層
    return operation;
移除緩存

可以通過(guò)removeImageForKey方法移除指定的緩存。這個(gè)操作也是異步的攒磨,需要放在ioQueue中執(zhí)行泳桦。

- (void)removeImageForKey:(nullable NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion {
    if (key == nil) {
        return;
    }

    if (self.config.shouldCacheImagesInMemory) {
        [self.memCache removeObjectForKey:key];
    }

    if (fromDisk) {
        dispatch_async(self.ioQueue, ^{
            [_fileManager removeItemAtPath:[self defaultCachePathForKey:key] error:nil];
            
            if (completion) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    completion();
                });
            }
        });
    } else if (completion){
        completion();
    }
    
}

除此之外,SDWebImage還支持批量移除咧纠,可以根據(jù)配置的緩存時(shí)間蓬痒,緩存大小等參數(shù)管理緩存數(shù)據(jù)的聲明周期。
使用deleteOldFilesWithCompletionBlock刪除時(shí)間太久的數(shù)據(jù)
使用clearMemory清除NSCache中的數(shù)據(jù)
使用clearDiskOnCompletion清除所有磁盤上的數(shù)據(jù)漆羔,這個(gè)函數(shù)會(huì)刪除創(chuàng)建的目錄結(jié)構(gòu)

小結(jié)

SDWebImage緩存主要分為內(nèi)存緩存和磁盤緩存兩部分梧奢,內(nèi)存緩存使用NSCache進(jìn)行緩存,在內(nèi)存占用過(guò)多時(shí)可以釋放多余內(nèi)存演痒。磁盤緩存使用NSFileManager實(shí)現(xiàn)亲轨,使用了一個(gè)串行隊(duì)列來(lái)保證操作的正確性,異步執(zhí)行讀寫操作保證不影響主線程鸟顺。提供配置項(xiàng)管理緩存策略讓內(nèi)存緩存和磁盤緩存協(xié)調(diào)工作惦蚊。防止了冗余的緩存操作。

相關(guān)文章

SDWebImage源碼閱讀筆記
SDWebImage源碼閱讀
SDWebImage 源碼閱讀筆記(二)
SDWebImage源碼閱讀筆記

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末讯嫂,一起剝皮案震驚了整個(gè)濱河市蹦锋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌欧芽,老刑警劉巖莉掂,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異千扔,居然都是意外死亡憎妙,警方通過(guò)查閱死者的電腦和手機(jī)库正,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)厘唾,“玉大人褥符,你說(shuō)我怎么就攤上這事「Ю” “怎么了喷楣?”我有些...
    開(kāi)封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)讯柔。 經(jīng)常有香客問(wèn)我抡蛙,道長(zhǎng)护昧,這世上最難降的妖魔是什么魂迄? 我笑而不...
    開(kāi)封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮惋耙,結(jié)果婚禮上捣炬,老公的妹妹穿的比我還像新娘。我一直安慰自己绽榛,他們只是感情好湿酸,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著灭美,像睡著了一般推溃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上届腐,一...
    開(kāi)封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天铁坎,我揣著相機(jī)與錄音,去河邊找鬼犁苏。 笑死硬萍,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的围详。 我是一名探鬼主播朴乖,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼助赞!你這毒婦竟也來(lái)了买羞?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤雹食,失蹤者是張志新(化名)和其女友劉穎畜普,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體婉徘,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡漠嵌,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年咐汞,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片儒鹿。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡化撕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出约炎,到底是詐尸還是另有隱情植阴,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布圾浅,位于F島的核電站掠手,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏狸捕。R本人自食惡果不足惜喷鸽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望灸拍。 院中可真熱鬧做祝,春花似錦、人聲如沸鸡岗。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)轩性。三九已至声登,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間揣苏,已是汗流浹背悯嗓。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留舒岸,地道東北人绅作。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蛾派,于是被迫代替她去往敵國(guó)和親俄认。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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