SDWebImage4.0源碼探究(一)面試題

目錄

  • 一、SDWebImage UML分析
  • 二、SDWebImage 中 @autoreleasepool 的應用
  • 三、SDWebImage 支持 GIF動圖 嗎?
  • 四、SDWebImage 如何 區分圖片格式
  • 五、SDWebImage 緩存圖片的名稱如何 避免重名
  • 六、SDWebImage 中 常量的定義
  • 七、SDWebImage 如何保證UI操作放在主線程中執行?
  • 八、SDWebImage 的 最大并發數超時時長
  • 九、SDWebImage 的Memory緩存和Disk緩存是用什么實現的?
  • 十、SDWebImage 讀取Memory和Disk的時候如何保證 線程安全
  • 十一、SDWebImage 的 Memory警告 是如何處理的!
  • 十二、SDWebImage Disk緩存時長? Disk清理操作時間點? Disk清理原則?
  • 十三、SDWebImage Disk目錄 位于哪里?
  • 十四、SDWebImage 的回調設計?
  • 十五、SDWebImage 中 NS_OPTIONSNS_ENUM 的使用
  • 十六、SDWebImage 中的工具類介紹
  • 【更新:2018-09-11】
  • 【推薦:2019-06-05 iOS開發·由SDWebImage引發的知識點聚合與思考(最新嘔心瀝血之作)

官方文檔

一、UML圖和時序圖

[圖片上傳失敗...(image-d49a02-1523894672845)]

[圖片上傳失敗...(image-e4a15e-1523894672845)]


二、SDWebImage 中@autoreleasepool的應用

現考慮如下代碼:

for (int i = 0; i < 10000; i++) {
    [self doSthWith:object];
}

這段代碼和筆試題關鍵部分大同小異。如果"doSthWith:"方法要創建一個臨時對象,那么這個對象很可能會放在自動釋放池里。筆試題中最后stringByAppendingString方法很有可能屬于上述的方法。因此如果涉及到了自動釋放池,那么問題也應該就出在上面。

注意:即便臨時對象在調用完方法后就不再使用了,它們也依然處于存活狀態,因為目前它們都在自動釋放池里,等待系統稍后進行回收。但自動釋放池卻要等到該線程執行下一次事件循環時才會清空,這就意味著在執行for循環時,會有持續不斷的新的臨時對象被創建出來,并加入自動釋放池。要等到結束for循環才會釋放。在for循環中內存用量會持續上漲,而等到結束循環后,內存用量又會突然下降。

而如果把循環內的代碼包裹在“自動釋放池”中,那么在循環中自動釋放的對象就會放在這個池,而不是在線程的主池里面。如下:

for (int i = 0; i < 1000000; i++) {
        @autoreleasepool {
            NSString *str = @"abc";
            str = [str lowercaseString];
            str = [str stringByAppendingString:@"xyz"];
        }        
}

新增的自動釋放池可以減少內存用量,因為系統會在塊的末尾把這些對象回收掉。而上述這些臨時對象,正在回收之列。

自動釋放池的機制就像“棧”。系統創建好池之后,將其壓入棧中,而清空自動釋放池相當于將池從棧中彈出。在對象上執行自動釋放操作,就等于將其放入位于棧頂的那個池。

結論:@autoreleasepool利于局部變量立刻釋放


三、SDWebImage 支持GIF動圖嗎?

3.1、SDWebImage 4.0版本之前的UIImage+GIF類別

SDWebImage這個庫里有一個UIImage+GIF的類別,里面為UIImage擴展了三個方法:

@interface UIImage (GIF)
+ (IImage *)sd_animatedGIFNamed:(NSString *)name;
+ (UIImage *)sd_animatedGIFWithData:(NSData *)data;
- (UIImage *)sd_animatedImageByScalingAndCroppingToSize:(CGSize)size;
@end

具體使用 參考文章

NSString *path = [[NSBundle mainBundle] pathForResource:@"gifTest" ofType:@"gif"];
NSData *data = [NSData dataWithContentsOfFile:path];
UIImage *image = [UIImage sd_animatedGIFWithData:data];
gifImageView.image = image;

3.2、SDWebImage 4.0版本之后的UIImage+GIF類別

SDWebImage這個庫里有一個UIImage+GIF的類別,其中的擴展方法只有一個sd_animatedGIFWithData :它只返回數據包含的第一幀的圖像

@interface UIImage (GIF)
/**
 *  Compatibility method - creates an animated UIImage from an NSData, it will only contain the 1st frame image
 */
+ (UIImage *)sd_animatedGIFWithData:(NSData *)data;

/**
 *  Checks if an UIImage instance is a GIF. Will use the `images` array
 */
- (BOOL)isGIF;
@end

具體使用

只返回一幀的圖像

結論:SDWebImage 4.0版本之后,sd_animatedGIFWithData :沒辦法實現gif加載;

3.3、SDWebImage 4.0版本之后 加載gif新方法

官方文檔:


Animated Images (GIF) support

  • Starting with the 4.0 version, we rely on FLAnimatedImage to take care of our animated images.
  • If you use cocoapods, add pod 'SDWebImage/GIF' to your podfile.
  • To use it, simply make sure you use FLAnimatedImageView instead of UIImageView.
  • Note: there is a backwards compatible feature, so if you are still trying to load a GIF into a UIImageView, it will only show the 1st frame as a static image by default. However, you can enable the full GIF support by using the built-in GIF coder. See GIF coder
  • Important: FLAnimatedImage only works on the iOS platform. For macOS, use NSImageView with animates set to YES to show the entire animated images and NO to only show the 1st frame. For all the other platforms (tvOS, watchOS) we will fallback to the backwards compatibility feature described above

結論:

  • 4.0版本之后,SD依賴 FLAnimatedImage進行了gif的加載,
  • 需要我們單獨導入pod 'SDWebImage/GIF'
  • 并且需要使用FLAnimatedImageView 代替 UIImageView
  • 注意事項:這里所說的就是版本兼容的問題,也就是我們之前討論的sd_animatedGIFWithData :方法沒辦法實現gif加載了。但是,您可以使用內置的GIF編碼器來啟用完整的GIF支持。具體查看 GIF coder
  • 重要事項:
    • FLAnimatedImage暫時僅支持iOS平臺;
    • macOS平臺使用NSImageViewanimates設置為YES以顯示整個動畫圖像,而不只是顯示第1幀。
    • 其他平臺(tvOS, watchOS),我們將退回到上面描述的向后兼容性特性。可以使用老版本的方法。

四、SDWebImage 如何區分圖片格式?

  • PNG:壓縮比沒有JPG高,但是無損壓縮,解壓縮性能高,蘋果推薦的圖像格式!
  • JPG:壓縮比最高的一種圖片格式,有損壓縮!最多使用的場景,照相機!解壓縮的性能不好!
  • GIF:序列楨動圖,特點:只支持256種顏色!最流行的時候在1998~1999,有專利的!

在分類"NSData+ImageContentType.h"中

typedef NS_ENUM(NSInteger, SDImageFormat) {
    SDImageFormatUndefined = -1,
    SDImageFormatJPEG = 0,
    SDImageFormatPNG,
    SDImageFormatGIF,
    SDImageFormatTIFF,
    SDImageFormatWebP
};

/**
 *  Return image format
 *
 *  @param data the input image data
 *
 *  @return the image format as `SDImageFormat` (enum)
 */
+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data;
+ (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;
}

實現思想:將數據data轉為十六進制數據,取第一個字節數據進行判斷。

查看圖片

五、SDWebImage 緩存圖片的名稱如何避免重名

對『絕對路徑』進行MD5

  • 如果單純使用 文件名保存,重名的幾率很高!
  • 使用 MD5 的散列函數!對完整的 URL 進行 md5,結果是一個 32 個字符長度的字符串!

六、SDWebImage 中常量的定義

可參考之前的文章宏(define)與常量(const)

  • SD中的FOUNDATION_EXPORT定義與調用
// SDWebImage-umbrella.h

#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
// 在SDWebImageCompat.h
FOUNDATION_EXPORT NSString *const SDWebImageErrorDomain;

// 在SDWebImageCompat.m
NSString *const SDWebImageErrorDomain = @"SDWebImageErrorDomain";
  • 我常用的UIKIT_EXTERN調用
UIKIT_EXTERN NSString * const CHECK_SUM_MQ;

NSString * const CHECK_SUM_MQ = @"123";
  • 系統內部對FOUNDATION_EXPORTUIKIT_EXTERN的定義
// 在系統內部文件 UIKitDefines.h 中

#ifdef __cplusplus
#define UIKIT_EXTERN        extern "C" __attribute__((visibility ("default")))
#else
#define UIKIT_EXTERN            extern __attribute__((visibility ("default")))
#endif
// 系統內部文件 NSObjCRuntime.h 中

#if defined(__cplusplus)
#define FOUNDATION_EXTERN extern "C"
#else
#define FOUNDATION_EXTERN extern
#endif

#if TARGET_OS_WIN32

    #if defined(NSBUILDINGFOUNDATION)
        #define FOUNDATION_EXPORT FOUNDATION_EXTERN __declspec(dllexport)
    #else
        #define FOUNDATION_EXPORT FOUNDATION_EXTERN __declspec(dllimport)
    #endif

    #define FOUNDATION_IMPORT FOUNDATION_EXTERN __declspec(dllimport)

#else
    #define FOUNDATION_EXPORT  FOUNDATION_EXTERN
    #define FOUNDATION_IMPORT FOUNDATION_EXTERN
#endif
  • 結論:
    • FOUNDATION_EXTERN 在 C 中 是 extern;在C++中是 extern 'C' ;其他情況則在win32情況下;
    • UIKIT_EXTERN簡單來說,就是將函數修飾為兼容以往C編譯方式的、具有extern屬性(文件外可見性)、public修飾的方法或變量庫外仍可見的屬性;

七、SDWebImage 如何保證UI操作放在主線程中執行?

iOS UI 操作在主線程不一定安全?

在SDWebImage的SDWebImageCompat.h中有這樣一個宏定義,用來保證主線程操作,為什么要這樣寫?

// SDWebImageCompat.h 中

#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block)\
    if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {\
        block();\
    } else {\
        dispatch_async(dispatch_get_main_queue(), block);\
    }
#endif

在此之前見到最多的是這樣的:

#define dispatch_main_async_safe(block)\
    if ([NSThread isMainThread]) {\
        block();\
    } else {\
        dispatch_async(dispatch_get_main_queue(), block);\
    }

對比兩段代碼可以發現前者有兩個地方改變了,一是多了 #ifndef,二是判斷條件改變了。

顯然,增加 #ifndef 是為了提高代碼的嚴謹,防止重復定義 dispatch_main_async_safe

關于判斷條件的改變的原因則是復雜得多了,可參考文檔

GCD's Main Queue vs. Main Thread

Queues are not bound to any specific thread

分析:如何判斷當前是否在main thread?

最簡單的方法

檢查我們當前在主線程上執行的最簡單的方法是使用[NSThread isMainThread] - GCD缺少一個類似的方便的API來檢查我們是否在主隊列上運行,因此許多開發人員使用了NSThread API。如下:

if ([NSThread isMainThread]) {
    block();
} else {
    dispatch_async(dispatch_get_main_queue(), block);
}

這在大多數情況下是有效的,直到它出現了異常。下面是關于ReactiveCocoa repo問題的摘錄:
ReactiveCocoa issue

image

潛在的問題是VektorKit API正在檢查是否在主隊列上調用它,而不是檢查它在主線程上運行。

雖然每個應用程序都只有一個主線程,但是在這個主線程上執行許多不同的隊列是可能的。

如果庫(如VektorKit)依賴于在主隊列上檢查執行,那么從主線程上執行的非主隊列調用API將導致問題。也就是說,如果在主線程執行非主隊列調度的API,而這個API需要檢查是否由主隊列上調度,那么將會出現問題。

更安全的方法一

從技術上講,我認為這是一個 MapKit / VektorKit 漏洞,蘋果的UI框架通常保證在從主線程調用時正確工作,沒有任何文檔提到需要在主隊列上執行代碼。

但是,現在我們知道某些api不僅依賴于主線程上的運行,而且還依賴于主隊列,因此檢查當前隊列而不是檢查當前線程更安全。

檢查當前隊列還可以更好地利用GCD為線程提供的抽象。從技術上講,我們不應該知道/關心主隊列是一種總是綁定到主線程的特殊隊列。

不幸的是,GCD沒有一個非常方便的API來檢查我們當前正在運行的隊列(這很可能是許多開發人員首先使用NSThread.isMainThread()的原因)。

我們需要使用 dispatch_queue_set_specific 函數來將鍵值對與主隊列相關聯;稍后,我們可以使用 dispatch_queue_get_specific 來檢查鍵和值的存在。

- (void)function {
    static void *mainQueueKey = "mainQueueKey";
    dispatch_queue_set_specific(dispatch_get_main_queue(), mainQueueKey, &mainQueueKey, NULL);
    if (dispatch_get_specific(mainQueueKey)) {
        // do something in main queue
        //通過這樣判斷,就可以真正保證(我們在不主動搞事的情況下),任務一定是放在主隊列中的
    } else {
        // do something in other queue
    }
}
更安全的方法二 (SDWebImage使用的方法)

我們知道在使用 GCD 創建一個 queue 的時候會指定 queue_label,可以理解為隊列名,就像下面:

dispatch_queue_t myQueue = dispatch_queue_create("com.apple.threadQueue", DISPATCH_QUEUE_SERIAL);

而第一個參數就是 queue_label,根據官方文檔解釋,這個queueLabel 是唯一的,所以SDWebImage就采用了這個方式

//取得當前隊列的隊列名
dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)
   
//取得主隊列的隊列名
dispatch_queue_get_label(dispatch_get_main_queue())

然后通過 strcmp 函數進行比較,如果為0 則證明當前隊列就是主隊列。

SDWebImage中的實例 :判斷當前是否是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");
    }
}

結論

SDWebImage 就是從判斷是否在主線程執行改為判斷是否由主隊列上調度。而由于主隊列是一個串行隊列,無論任務是異步同步都不會開辟新線程,所以當前隊列是主隊列等價于當前在主線程上執行。可以這樣說,在主隊列調度的任務肯定在主線程執行,而在主線程執行的任務不一定是由主隊列調度的。


八、SDWebImage 的最大并發數 和 超時時長

// SDWebImageDownloader.m   -initWithSessionConfiguration:

_downloadQueue.maxConcurrentOperationCount = 6;
_downloadTimeout = 15.0;

九、SDWebImage 的Memory緩存和Disk緩存是用什么實現的?

9.1、Memory緩存實現 -- AutoPurgeCache

『AutoPurgeCache』類繼承自 『NSCache』

SDWebImage 還專門實現了一個叫做 AutoPurgeCache 的類 繼承自 NSCache ,相比于普通的 NSCache它提供了一個在內存緊張時候釋放緩存的能力。

  • 自動刪除機制:當系統內存緊張時,NSCache 會自動刪除一些緩存對象
  • 線程安全:從不同線程中對同一個 NSCache 對象進行增刪改查時,不需要加鎖
  • 不同于 NSMutableDictionaryNSCache存儲對象時不會對 key 進行 copy 操作
@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

9.2、Disk緩存實現 -- NSFileManager

SDImageCache 的磁盤緩存是通過異步操作 NSFileManager 存儲緩存文件到沙盒來實現的。


十、讀取Memory和Disk的時候如何保證線程安全?

10.1、讀取Memory

NScache是線程安全的,在多線程操作中,不需要對Cache加鎖。
讀取緩存的時候是在主線程進行。由于使用NSCache進行存儲、所以不需要擔心單個value對象的線程安全。

10.2、讀取Disk

  • 創建了一個名為 IO的串行隊列,所有Disk操作都在此隊列中,逐個執行!!
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t ioQueue;


// Create IO serial queue
_ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);
        
  • 判斷當前是否是IOQueue (原理:七、SDWebImage 如何保證UI操作放在主線程中執行?)
- (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");
    }
}
  • 在主要存儲函數中,dispatch_async(self.ioQueue, ^{})
// SDImageCache.m

- (void)storeImage:(nullable UIImage *)image
         imageData:(nullable NSData *)imageData
            forKey:(nullable NSString *)key
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock {
    // .........    
    
    if (toDisk) {
        dispatch_async(self.ioQueue, ^{
            @autoreleasepool {
                NSData *data = imageData;
                if (!data && image) {
                    SDImageFormat imageFormatFromData = [NSData sd_imageFormatForImageData:data];
                    data = [image sd_imageDataAsFormat:imageFormatFromData];
                }                
                [self storeImageDataToDisk:data forKey:key];
            }
            
            if (completionBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    completionBlock();
                });
            }
        });
    }
    
    // .........
}

結論:

  • 真正的磁盤緩存是在另一個IO專屬線程中的一個串行隊列下進行的。
  • 如果你搜索self.ioQueue還能發現、不只是讀取磁盤內容。
  • 包括刪除、寫入等所有磁盤內容都是在這個IO線程進行、以保證線程安全。
  • 但計算大小、獲取文件總數等操作。則是在主線程進行。(看下面代碼
// SDImageCache.m

- (NSUInteger)getSize {
    __block NSUInteger size = 0;
    dispatch_sync(self.ioQueue, ^{
        NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath];
        for (NSString *fileName in fileEnumerator) {
            NSString *filePath = [self.diskCachePath stringByAppendingPathComponent:fileName];
            NSDictionary<NSString *, id> *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
            size += [attrs fileSize];
        }
    });
    return size;
}

分析:我們可以看見,不會創建新線程且切操作會順序執行。你可能會疑惑:為什么同樣都是在主線程執行,這樣沒有死鎖。其實這個和線程沒有關系,和隊列有關系,只要不放在主隊列就不會阻塞主隊列上的操作(各種系統的UI方法),這個操作只是選擇了合適的時機在主線程上跑了一下而已~

10.3、SD使用 @synchronized

@synchronized (self.failedURLs) {
    isFailedUrl = [self.failedURLs containsObject:url];
}

結論:所有可能引起資源搶奪的對象操作、全部有條件鎖保護。
使用@synchronized來使得代碼獲得原子性,從而保證多線程安全。


十一、SDWebImage 的Memory警告是如何處理的!

利用通知中心觀察

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(clearMemory)
                                             name:UIApplicationDidReceiveMemoryWarningNotification
                                           object:nil];
  • UIApplicationDidReceiveMemoryWarningNotification 接收到內存警告的通知
    • 執行 clearMemory 方法,清理內存緩存!

十二、SDWebImage Disk緩存時長? Disk清理操作時間點? Disk清理原則?

12.1、默認為一周

// SDImageCacheConfig.m

static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week

12.2、磁盤清理時間點

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

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

分別在『應用被殺死時』和 『應用進入后臺時』進行清理操作

清理磁盤的方法

- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock;

當應用進入后臺時,會涉及到『Long-Running Task
正常程序在進入后臺后、雖然可以繼續執行任務。但是在時間很短內就會被掛起待機。
Long-Running可以讓系統為app再多分配一些時間來處理一些耗時任務。

- (void)backgroundDeleteOldFiles {
    Class UIApplicationClass = NSClassFromString(@"UIApplication");
    if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
        return;
    }
    UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)];
// 后臺任務標識--注冊一個后臺任務
    __block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
        // Clean up any unfinished task business by marking where you
        // stopped or ending the task outright.
        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];

    // Start the long-running task and return immediately.
    [self deleteOldFilesWithCompletionBlock:^{
//結束后臺任務
        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];
}

12.3、磁盤清理原則

清理緩存的規則分兩步進行。 第一步先清除掉過期的緩存文件。 如果清除掉過期的緩存之后,空間還不夠。 那么就繼續按文件時間從早到晚排序,先清除最早的緩存文件,直到剩余空間達到要求。

具體點,SDWebImage 是怎么控制哪些緩存過期,以及剩余空間多少才夠呢? 通過兩個屬性:

@interface SDImageCacheConfig : NSObject

/**
 * The maximum length of time to keep an image in the cache, in seconds
 */
@property (assign, nonatomic) NSInteger maxCacheAge;

/**
 * The maximum size of the cache, in bytes.
 */
@property (assign, nonatomic) NSUInteger maxCacheSize;

maxCacheAge 和 maxCacheSize 有默認值嗎?

  • maxCacheAge 在上述已經說過了,是有默認值的 1week,單位秒。
  • maxCacheSize 翻了一遍 SDWebImage 的代碼,并沒有對 maxCacheSize 設置默認值。 這就意味著 SDWebImage 在默認情況下不會對緩存空間設限制。可以這樣設置:
[SDImageCache sharedImageCache].maxCacheSize = 1024 * 1024 * 50;    // 50M

maxCacheSize 是以字節來表示的,我們上面的計算代表 50M 的最大緩存空間。 把這行代碼寫在你的 APP 啟動的時候,這樣 SDWebImage 在清理緩存的時候,就會清理多余的緩存文件了。

十三、SDWebImage Disk目錄位于哪里?

  • 緩存在沙盒目錄下 Library/Caches
  • 默認情況下,二級目錄為 ~/Library/Caches/default/com.hackemist.SDWebImageCache.default
  • 也可自定義文件名
- (instancetype)init {
    return [self initWithNamespace:@"default"];
}

- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns {
    NSString *path = [self makeDiskCachePath:ns];
    return [self initWithNamespace:ns diskCacheDirectory:path];
}

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

如何打開 真機和模擬器 沙盒文件

  • 模擬器
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) objectAtIndex:0];

通過 Finder -> 前往 -> 前往文件夾 -> 將路徑輸入即可!

  • 真機
image
image

通過 查看包內容 查看即可!

十四、SDWebImage 的回調設計?

  • Block
    單個圖片的分類、單個圖片的下載。
    每個操作任務中必現的progress以及completed。
    所以、有很強的個體綁定需要或者使用次數不多時、傾向使用block
  • Delegate
    SDWebImageManager下載完成之后的自定義圖片處理、是否下載某個url。
    這兩個方法如果需要的話都是將會調用多次的。所以、用Delegate更好、可以將方法常駐。
  • 同理
    UITableView的使用Delegate、是用為在滾動途中、代理方法需要被不斷的執行。
    UIButton也是將會被多次點擊。
    UIView的動畫/GCD則可以使用Block、因為只執行一次、用完釋放。
    所以、在日常使用中、我們也可以參考上述原則進行設計。

十五、SDWebImage 中 NS_OPTIONSNS_ENUM 的使用

/// SDWebImageManager.h      Line 14

typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
    SDWebImageRetryFailed = 1 << 0,                    // 值為2的0次方
    SDWebImageLowPriority = 1 << 1,                    // 值為2的1次方
    SDWebImageCacheMemoryOnly = 1 << 2,                // 值為2的2次方
    SDWebImageProgressiveDownload = 1 << 3,            // 值為2的3次方
    SDWebImageRefreshCached = 1 << 4,                  // 值為2的4次方
    SDWebImageContinueInBackground = 1 << 5,           // 值為2的5次方
    SDWebImageHandleCookies = 1 << 6,                  // 值為2的6次方
    SDWebImageAllowInvalidSSLCertificates = 1 << 7,    // 值為2的7次方
    SDWebImageHighPriority = 1 << 8,
    SDWebImageDelayPlaceholder = 1 << 9,
    SDWebImageTransformAnimatedImage = 1 << 10,
    SDWebImageAvoidAutoSetImage = 1 << 11,
    SDWebImageScaleDownLargeImages = 1 << 12
};
/// SDImageCache.h     Line 13

typedef NS_ENUM(NSInteger, SDImageCacheType) {
    SDImageCacheTypeNone,             // 默認從0開始
    SDImageCacheTypeDisk,             // 值為1
    SDImageCacheTypeMemory            // 值為2
};

NS_ENUM 定義 通用枚舉
NS_OPTIONS 定義 位移枚舉

位移枚舉即是在你需要的地方可以同時存在多個枚舉值如這樣:

[gifImageView sd_setImageWithURL:url placeholderImage:image options:SDWebImageRefreshCached | SDWebImageRetryFailed progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {

            } completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
 
            }];

而NS_ENUM定義的枚舉不能幾個枚舉項同時存在,只能選擇其中一項,像這樣:

// SDImageCache.m      Line 407

doneBlock(diskImage, diskData, SDImageCacheTypeDisk);

思考

/// SDWebImageManager.m    Line 157

SDWebImageDownloaderOptions downloaderOptions = 0;
if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;

分析

若 options = SDWebImageLowPriority | SDWebImageCacheMemoryOnly | SDWebImageProgressiveDownload 

| 運算規則:只要兩個對應的二進制位有一個為1,結果位就為1,否則為0;
& 運算規則:只有兩個對應的二進制位都為1時,結果位才為1,否則為0;

轉換為二進制
options = 0001 | 0010 | 0100
SDWebImageLowPriority = 0001

options = 0111
SDWebImageLowPriority = 0001

if (options & SDWebImageLowPriority)
=== if(0111 & 0001)
=== if(0001)
=== if(2)

如果SDWebImageLowPriority = 1000
if (options & SDWebImageLowPriority)
=== if(0111 & 1000)
=== if(0000)
=== if(0)

十六、SDWebImage 中的工具類介紹

工具類深入研讀

  • NSData+ImageContentType: 根據圖片數據獲取圖片的類型,比如GIF、PNG等。
  • SDWebImageCompat: 根據屏幕的分辨倍數成倍放大或者縮小圖片大小。
  • SDImageCacheConfig: 圖片緩存策略記錄。比如是否解壓縮、是否允許iCloud、是否允許內存緩存、緩存時間等。默認的緩存時間是一周。
  • UIImage+MultiFormat: 獲取UIImage對象對應的data、或者根據data生成指定格式的UIImage,其實就是UIImage和NSData之間的轉換處理。
  • UIImage+GIF: 對于一張圖片是否GIF做判斷。可以根據NSData返回一張GIF的UIImage對象,并且只返回GIF的第一張圖片生成的GIF。如果要顯示多張GIF,使用FLAnimatedImageView。
  • SDWebImageDecoder: 根據圖片的情況,做圖片的解壓縮處理。并且根據圖片的情況決定如何處理解壓縮。

【更新1:2018-09-11】

第三條、SDWebImage 支持 GIF動圖 嗎?

根據 SDWebImage 4.4.2 版本測試,得出 "UIImage+GIF.h"sd_animatedGIFWithData 可以 支持 GIF動圖,又可以愉快的玩耍了。

SDWebImage 4.4.2 版本測試結果

參考文檔


完結

歡迎指正補充,可聯系lionsom_lin@qq.com
原文地址:SDWebImage4-0源碼探究(二)框架分析

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

推薦閱讀更多精彩內容