關(guān)于SDWebImage源碼常見問題

SDWebImage.png

簡析

前段時(shí)間卧秘,和一個(gè)小伙交流鳖链,那小伙問我:
小伙:“NSString聲明屬性時(shí)姆蘸,用什么修飾?”
我:“copy”
小伙:“為什么用copy芙委,用strong有什么問題么逞敷?”
我:“如果使用strong修飾,只是對字符串做了淺拷貝,當(dāng)某個(gè)對象持有這個(gè)屬性時(shí)灌侣,會改變這個(gè)屬性值推捐。”
小伙:“那我就想讓它改變呢侧啼?”
我:“......(⊙o⊙)?”

出來混也有三年了牛柒,竟然又在最基礎(chǔ)的上面栽了,好尷尬痊乾。其實(shí)說到底還是自己內(nèi)功修為不夠焰络。蜻蜓點(diǎn)水,對于開發(fā)者而言是大忌符喝,做過幾款A(yù)PP就覺得自己怎樣怎樣闪彼,真真是井底之蛙。
做為iOS開發(fā)者协饲,相信大家都會或多或少的使用或了解過SDWebImage畏腕,剖析其源碼的文章不在少數(shù),今天我從問題驅(qū)動的角度來簡單梳理下我所理解的SDWebImage茉稠。

SDWebImages圖片類型識別問題

大家都知道描馅,UIImageView默認(rèn)情況下只能加載png類型的圖片,加載jpg/gif等類型時(shí)是要單獨(dú)處理的而线,那么SDWebImage是怎么識別網(wǎng)絡(luò)圖片的類型呢铭污?
閱讀源碼,大家會發(fā)現(xiàn)在NSData的分類文件NSData+ImageContentType.m中膀篮,它是根據(jù)文件頭來識別嘹狞,即圖片流文件的第一個(gè)字節(jié)判斷。

#import "NSData+ImageContentType.h"
@implementation NSData (ImageContentType)

+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data {
    if (!data) {
        return SDImageFormatUndefined;
    }
    uint8_t c;
    [data getBytes:&c length:1];
    switch (c) {
        case 0xFF:
            return SDImageFormatJPEG;
        case 0x89:
            return SDImageFormatPNG;
        case 0x47:
            return SDImageFormatGIF;
        case 0x49:
        case 0x4D:
            return SDImageFormatTIFF;
        case 0x52:
            // R as RIFF for WEBP
            if (data.length < 12) {
                return SDImageFormatUndefined;
            }
            NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
            if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
                return SDImageFormatWebP;
            }
    }
    return SDImageFormatUndefined;
}
@end

OpenCV圖片類型識別也類似誓竿,參見:include1224的博客:讀文件頭判斷圖片類型

SDWebImage的下載隊(duì)列機(jī)制

SDWebImage加載網(wǎng)絡(luò)圖片的方式是異步加載的方式磅网,不管是從性能方面還是從為用戶節(jié)省流量的角度而言,SDWebImage做的都是比較好的筷屡。
那么涧偷,問題來了:
1簸喂、 異步加載多張圖片時(shí),SDWebImage是怎么處理的燎潮?是否有對應(yīng)的并發(fā)隊(duì)列喻鳄?
2、如果有确封,它的并發(fā)隊(duì)列運(yùn)行機(jī)制是怎樣的呢除呵?既然是并發(fā)隊(duì)列,最大的并發(fā)數(shù)是多少隅肥?
3竿奏、當(dāng)多個(gè)圖片下載任務(wù)結(jié)束時(shí),在隊(duì)列中移除的策略是怎樣的腥放,是先進(jìn)先出泛啸?還是后進(jìn)先出?
4秃症、當(dāng)某個(gè)圖片的URL為錯誤鏈接候址,或者服務(wù)器異常,或者網(wǎng)絡(luò)異常的情況下种柑,SDWebImage有沒有異常超時(shí)處理岗仑?如果有超時(shí)機(jī)制,時(shí)長是多少呢聚请?
下面我將一一為大家找到答案:
SDWebImage網(wǎng)絡(luò)圖片下載是通過SDWebImageDownloader和SDWebImageDownloaderOperation類來完成的荠雕。

  • SDWebImageDownloaderOperation封裝了單個(gè)圖片下載操作,它有一個(gè)start方法用來開啟一個(gè)下載任務(wù)驶赏,看源碼可以看到炸卑,在start方法體中有一段:
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.timeoutIntervalForRequest = 15;

在內(nèi)部明確寫了單個(gè)任務(wù)的超時(shí)時(shí)間15秒。

  • SDWebImageDownloader是用來管理SDWebImageDownloaderOperation圖片下載任務(wù)的(另外在SDWebImageDownloader中也可以配置任務(wù)超時(shí)時(shí)長)煤傍,它持有多個(gè)公有屬性:maxConcurrentDownloads(最大并發(fā)數(shù))盖文、downloadTimeout(任務(wù)超時(shí)時(shí)長)、executionOrder(隊(duì)列執(zhí)行方式)等蚯姆,維護(hù)著一個(gè)私有并發(fā)下載隊(duì)列downloadQueue和一個(gè)最新任務(wù)添加任務(wù)lastAddedOperation五续。
    看源碼我們可以輕松了解到,SDWebImage的下載隊(duì)列默認(rèn)情況下是SDWebImageDownloaderFIFOExecutionOrder龄恋,是先進(jìn)先出的疙驾,下載隊(duì)列并發(fā)數(shù)為6。
downloadQueue.maxConcurrentOperationCount = 6;
downloadTimeout: 15.0;
executionOrder: SDWebImageDownloaderFIFOExecutionOrder;
  • 詳見源碼:
@interface SDWebImageDownloader () 
@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;
......
@end
- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration {
    if ((self = [super init])) {
        _operationClass = [SDWebImageDownloaderOperation class];
        _shouldDecompressImages = YES;
        _executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
        _downloadQueue = [NSOperationQueue new];
        _downloadQueue.maxConcurrentOperationCount = 6;
        _downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
        _URLOperations = [NSMutableDictionary new];
#ifdef SD_WEBP
        _HTTPHeaders = [@{@"Accept": @"image/webp,image/*;q=0.8"} mutableCopy];
#else
        _HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy];
#endif
        _barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
        _downloadTimeout = 15.0;
        sessionConfiguration.timeoutIntervalForRequest = _downloadTimeout;
        /**
         *  Create the session for this task
         *  We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
         *  method calls and completion handler calls.
         */
        self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration
                                                     delegate:self
                                                delegateQueue:nil];
    }
    return self;
}

SDWebImage緩存機(jī)制

SDWebImage緩存機(jī)制其實(shí)由兩部分組成:內(nèi)存緩存篙挽、磁盤緩存荆萤。從SDImageCache文件中我們可以清楚地看出這一點(diǎn),其中memCache即內(nèi)存緩存铣卡,diskCachePath即磁盤緩存链韭,數(shù)據(jù)文件存儲在沙盒中:

@interface SDImageCache ()
@property (strong, nonatomic, nonnull) NSCache *memCache;
@property (strong, nonatomic, nonnull) NSString *diskCachePath;
......
@end

內(nèi)存緩存
先說下內(nèi)存緩存memCache,為了完善內(nèi)存緩存煮落,SDWebImage實(shí)現(xiàn)了NSCache的一個(gè)子類AutoPurgeCache敞峭,擴(kuò)充了NSCache,當(dāng)內(nèi)存警告時(shí)蝉仇,它會接受UIApplicationDidReceiveMemoryWarningNotification通知旋讹,自動執(zhí)行removeAllObjects操作。

@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;
}
- (void)dealloc {
#if SD_UIKIT
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
@end

如果大家細(xì)心的話會發(fā)現(xiàn)轿衔,SDWebImage做了內(nèi)存緩存沉迹,當(dāng)我們頻繁的使用SDWebImage加載多張圖片時(shí),卻為何基本不會出現(xiàn)內(nèi)存暴漲的情況呢害驹?其實(shí)這一切歸功于自動釋放池@autoreleasepool鞭呕。
磁盤緩存
接下來咱們說下磁盤緩存,磁盤緩存文件是存儲在沙盒中的宛官,存儲過程比較復(fù)雜葫松。我先簡單說下,SDWebImage加載圖片的大致流程底洗,相信從中腋么,大家會對diskCache有所了解。
在使用SDWebImage時(shí)亥揖,往往是從UIImageView+WebCache文件開始的珊擂,我們使用SDWebImage第一步就是要引入U(xiǎn)IImageView的分類WebCache,然后調(diào)用sd_setImageWithURL:方法费变,完成圖片的異步加載摧扇。
圖片加載的具體流程如下:

  • 調(diào)用sd_setImageWithURL方法時(shí),它首先是通過URL作為key查詢內(nèi)存緩存胡控,即SDImageCache的memCache屬性扳剿,如果存在直接顯示到View上。
  • 反之昼激,將通過md5編碼URL作為文件名庇绽,去沙盒(即SDImageCache的diskCachePath路徑下)中查詢有無此文件,如果存在橙困,就把沙盒中的文件加載到內(nèi)存緩存memCache中瞧掺,然后通過SDWebImageDecoder解碼后,直接顯示到View上凡傅。
  • 如果沙盒中不存在辟狈,則先將占位圖片placeholderImage加載到View上,緊接著去SDWebImageDownloader的downloadQueue隊(duì)列中,查找是否有正在下載該圖片的下載任務(wù)哼转,如果存在繼續(xù)該任務(wù)明未。
  • 如果下載隊(duì)列不存在,創(chuàng)建圖片下載任務(wù)SDWebImageDownloaderOperation壹蔓,然后通過lastAddedOperation趟妥,根據(jù)對應(yīng)的機(jī)制添加到下載并發(fā)隊(duì)列downloadQueue中,下載完畢后佣蓉,將操作在隊(duì)列中移除披摄,將圖片添加到內(nèi)存緩存中,直接顯示到View勇凭,并將該文件壓縮編碼后存儲到沙盒中疚膊,將通過md5編碼URL作為文件名。

相信看到上面的流程后虾标,大家對磁盤緩存機(jī)制有所了解寓盗,當(dāng)然也帶來了一些疑問,比如:
1夺巩、圖片文件為什么使用md5編碼的URL作為文件名贞让?
2、磁盤緩存柳譬,既然稱為緩存喳张,就肯定有一定的時(shí)間期限,緩存的時(shí)長是多少美澳?
3销部、文件過期之后,在什么時(shí)機(jī)清除過期圖片文件的制跟?
4舅桩、沙盒大小是有限度的,那么為SDWebImage預(yù)留的磁盤空間有沒有大小限制雨膨?
5擂涛、如果我想清空所有的SDWebImage緩存怎么清除?如果我們需要清除特定的圖片緩存又該怎么處理聊记?
下文撒妈,我將為大家一一解答這一系列疑問:

SDWebImage緩存圖片命名問題

SDWebImage是怎樣維護(hù)緩存圖片的?在SDImageCache文件中排监,我們不難發(fā)現(xiàn)狰右,它是利用了MD5的壓縮性特性、容易計(jì)算舆床、強(qiáng)抗碰撞等特性棋蚌,將圖片的URL進(jìn)行md5編碼嫁佳,作為文件名存儲到沙盒中的。

MD5百度百科
MD5算法具有以下特點(diǎn):
1谷暮、壓縮性:任意長度的數(shù)據(jù)蒿往,算出的MD5值長度都是固定的。
2坷备、容易計(jì)算:從原數(shù)據(jù)計(jì)算出MD5值很容易熄浓。
3情臭、抗修改性:對原數(shù)據(jù)進(jìn)行任何改動省撑,哪怕只修改1個(gè)字節(jié),所得到的MD5值都有很大區(qū)別俯在。
4竟秫、強(qiáng)抗碰撞:已知原數(shù)據(jù)和其MD5值,想找到一個(gè)具有相同MD5值的數(shù)據(jù)(即偽造數(shù)據(jù))是非常困難的跷乐。

- (nullable NSString *)cachePathForKey:(nullable NSString *)key inPath:(nonnull NSString *)path {
    NSString *filename = [self cachedFileNameForKey:key];
    return [path stringByAppendingPathComponent:filename];
}
- (nullable NSString *)defaultCachePathForKey:(nullable NSString *)key {
    return [self cachePathForKey:key inPath:self.diskCachePath];
}
- (nullable NSString *)cachedFileNameForKey:(nullable NSString *)key {
    const char *str = key.UTF8String;
    if (str == NULL) {
        str = "";
    }
    unsigned char r[CC_MD5_DIGEST_LENGTH];
    CC_MD5(str, (CC_LONG)strlen(str), r);
    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緩存文件保留時(shí)長及緩存空間大小

既然是緩存肥败,肯定有相應(yīng)的時(shí)間期限,默認(rèn)情況下SDWebImage的緩存時(shí)長為一周愕提,并且緩存空間可以自定義馒稍。

#import "SDImageCacheConfig.h"
static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week
@implementation SDImageCacheConfig

- (instancetype)init {
    if (self = [super init]) {
        _shouldDecompressImages = YES;
        _shouldDisableiCloud = YES;
        _shouldCacheImagesInMemory = YES;
        _maxCacheAge = kDefaultCacheMaxCacheAge;
        _maxCacheSize = 0;
    }
    return self;
}
@end
過濾URL,禁用緩存

如果想過濾特定URL浅侨,不使用緩存機(jī)制纽谒,可以在對應(yīng)位置加入如下代碼過濾。

SDWebImageManager.sharedManager.cacheKeyFilter = ^NSString * _Nullable(NSURL * _Nullable url) {

        url = [[NSURL alloc] initWithScheme:url.scheme host:url.host path:url.path];
        NSLog(@"url.scheme:%@, url.host:%@, url.path: %@", url.scheme, url.host, url.path);
        // if([[url.host absoluteString] isEqualToString:@"upload-images.jianshu.io"])
        if ([[url absoluteString] isEqualToString:@"http://upload-images.jianshu.io/upload_images/949086-5d2c51f1e3a9cddd.png"])
        {
            return nil;
        }
        return [url absoluteString];
    };
清除特定圖片緩存

剛說過如输,SDWebImage加載圖片是有緩存的鼓黔,默認(rèn)存儲一周的時(shí)間。使用SDWebImage加載同樣URL的圖片時(shí)不见,優(yōu)先會從緩存中取澳化,而不是每次重新請求加載,那么問題來了稳吮,我們的頭像/廣告圖等缎谷,需要實(shí)時(shí)刷新,我們要需要清除特定的圖片緩存灶似。
單單就頭像/廣告圖更新問題而言列林,無非是更新緩存問題,有很多方法解決喻奥,

  • 使用options:SDWebImageRefreshCached刷新緩存席纽,但是有些童鞋反應(yīng)該方法有閃爍問題,甚至有時(shí)并沒有更新圖片撞蚕,所以保險(xiǎn)起見润梯,最好還是手動清緩存的方式。
  • 每次清除掉圖片緩存,重新加載的方式纺铭,代碼如下:
    NSURL *imageURL = [NSURL URLWithString:@"http://upload-images.jianshu.io/upload_images/949086-5d2c51f1e3a9cddd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/999"];
// 獲取對應(yīng)URL鏈接的key
    NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:imageURL];
    NSString *pathStr = [[SDImageCache sharedImageCache] defaultCachePathForKey:key];
    NSLog(@"key存儲的路徑: %@", pathStr);
// 刪除對應(yīng)key的文件
    [[SDImageCache sharedImageCache] removeImageForKey:key withCompletion:^{
        [self.tempImageView sd_setImageWithURL:imageURL placeholderImage:[UIImage imageNamed:@"placeholderHead.png"]];
    }];

清除過期文件的時(shí)機(jī)

通過上文的解答寇钉,大家知道磁盤緩存的文件是有時(shí)間期限的,那么舶赔,SDWebImage在什么時(shí)機(jī)清除過期文件的呢扫倡?在SDImageCache文件我們同樣可以得到答案:
清除過期舊文件的時(shí)間點(diǎn)有兩處:程序切到后臺、殺死APP時(shí)竟纳。

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

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

具體源碼如下:

@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;
}
- (void)dealloc {
#if SD_UIKIT
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}

@end

本文已在版權(quán)印備案撵溃,如需轉(zhuǎn)載請?jiān)诎鏅?quán)印獲取授權(quán)。
獲取版權(quán)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末锥累,一起剝皮案震驚了整個(gè)濱河市缘挑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌桶略,老刑警劉巖语淘,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異际歼,居然都是意外死亡惶翻,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門鹅心,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吕粗,“玉大人,你說我怎么就攤上這事巴帮∷萜” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵榕茧,是天一觀的道長垃沦。 經(jīng)常有香客問我,道長用押,這世上最難降的妖魔是什么肢簿? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮蜻拨,結(jié)果婚禮上鸽照,老公的妹妹穿的比我還像新娘溅话。我一直安慰自己贩猎,他們只是感情好蜗顽,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著血崭,像睡著了一般卧惜。 火紅的嫁衣襯著肌膚如雪厘灼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天咽瓷,我揣著相機(jī)與錄音设凹,去河邊找鬼。 笑死茅姜,一個(gè)胖子當(dāng)著我的面吹牛闪朱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播钻洒,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼奋姿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了航唆?” 一聲冷哼從身側(cè)響起胀蛮,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎糯钙,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體退腥,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡任岸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了狡刘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片享潜。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖嗅蔬,靈堂內(nèi)的尸體忽然破棺而出剑按,到底是詐尸還是另有隱情,我是刑警寧澤澜术,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布艺蝴,位于F島的核電站,受9級特大地震影響鸟废,放射性物質(zhì)發(fā)生泄漏猜敢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一盒延、第九天 我趴在偏房一處隱蔽的房頂上張望缩擂。 院中可真熱鬧,春花似錦添寺、人聲如沸胯盯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽博脑。三九已至楞捂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間趋厉,已是汗流浹背寨闹。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留君账,地道東北人繁堡。 一個(gè)月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像乡数,于是被迫代替她去往敵國和親椭蹄。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

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