SDWebImage
一個支持遠程服務器圖片加載緩存的庫
功能簡介
-
UIImageView
寂殉,UIButton
,MKAnnotationView
添加Web圖像和緩存管理的類別 - 一個異步圖片下載器
- 一個異步內(nèi)存磁盤圖片緩存且自動處理過期圖片
- 背景圖片壓縮
- 保證同一個URL不會被多次下載
- 保證不會一次又一次地重試偽造的URL
- 保證主線程永遠不會被阻塞
- 性能窿冯!
- 使用GCD和ARC
工作流程
- 入口
sd_setImageWithURL:placeholderImage:options:progress:completed:
會先取消上次的加載操作,再設置 placeholderImage 顯示,然后 SDWebImageManager 根據(jù) URL 開始處理圖片姆钉。 - 進入 **SDWebImageManager ** 中的
loadImageWithURL:options:progress:completed:
吆倦,交給 SDImageCache 從緩存中查找圖片 - 先從內(nèi)存緩存查找是否有圖片
imageFromMemoryCacheForKey:
听诸,如果內(nèi)存中已經(jīng)有圖片緩存,直接調(diào)用 SDCacheQueryCompletedBlock蚕泽。 - SDWebImageManager 回調(diào) SDInternalCompletionBlock 到 UIView+WebCache 等前端展示圖片晌梨。
- 如果內(nèi)存緩存中沒有,生成
queryDiskBlock
添加到隊列中開始從硬盤查找圖片须妻。 - 根據(jù)哈希之后的 URL Key 在磁盤緩存目錄下查找圖片仔蝌,這一步是根據(jù) SDImageCacheOptions 決定同步查找還是異步在 ioQueue 隊列中查找,查找完成后將圖片添加到內(nèi)存緩存中荒吏,然后異步回到主線程中再返回圖片給 SDWebImageManager敛惊。
- 如果緩存中獲取不到圖片,則通過 SDWebImageDownloader 下載圖片绰更。
- 如果該URL已存在下載操作 NSOperation<SDWebImageDownloaderOperationInterface> operation (默認為 SDWebImageDownloaderOperation 類型)瞧挤,則將當前所對應的 progressBlock 和 completedBlock 添加到該 operation 的 callbackBlocks 數(shù)組中,圖片下載由 **NSURLSession ** 來做动知。
- 在 SDWebImageDownloaderOperation 中的
URLSession:dataTask:didReceiveData:
中實現(xiàn)邊下載邊解碼圖片 - 下載完圖片之后皿伺,遍歷 callbackBlocks 數(shù)組中的所有完成回調(diào)操作,將下載到的二進制數(shù)據(jù)和圖片返回給 SDWebImageManager盒粮,SDWebImageManager 將圖片添加到緩存中鸵鸥。
源碼分析
Cache
減少網(wǎng)絡請求次數(shù),節(jié)省流量丹皱,下載完圖片后存儲到本地妒穴,下載再獲取同一個URL時,優(yōu)先從本地獲取摊崭,提升用戶體驗讼油。
SDWebImage 對圖片進行緩存工作主要由 SDImageCache 完成。主要用于處理內(nèi)存緩存和磁盤緩存呢簸,其中磁盤緩存的寫操作是異步的矮台,不會對UI造成影響乏屯。
內(nèi)存緩存
內(nèi)存緩存采用的是 NSCache + NSMapTable 雙重緩存機制,SDMemoryCache 繼承于 NSCache瘦赫, 會自動處理內(nèi)存緩存問題辰晕,并在收到內(nèi)存警告的時候,移除自身所緩存的內(nèi)存資源确虱。但是SDMemoryCache 中的 weakCache 并不會在收到內(nèi)存警告的時候清除含友。
self.weakCache = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
weakCache 中存儲的 key
值是對URLKey 的強引用,而 value
則是對 UIImage 的弱引用校辩,并不會額外占用內(nèi)存資源窘问。
磁盤緩存
磁盤緩存的處理通過 NSFileManager 對象實現(xiàn),圖片存儲的位置位于 cache 文件夾宜咒,還可以設置 customPaths 數(shù)組來自定義磁盤查詢目錄惠赫。另外 SDImageCache 中還有 ioQueue 串行隊列來異步查詢存儲圖片。
存圖片
存儲圖片API:
- (void)storeImage:(nullable UIImage *)image forKey:(nullable NSString *)key completion:(nullable SDWebImageNoParamsBlock)completionBlock;
- (void)storeImage:(nullable UIImage *)image forKey:(nullable NSString *)key toDisk:(BOOL)toDisk completion:(nullable SDWebImageNoParamsBlock)completionBlock;
- (void)storeImage:(nullable UIImage *)image imageData:(nullable NSData *)imageData forKey:(nullable NSString *)key toDisk:(BOOL)toDisk completion:(nullable SDWebImageNoParamsBlock)completionBlock;
- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key;
先保存到內(nèi)存緩存中荧呐,同時保存對這張圖片的一個弱引用
/// SDMemoryCache
- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g {
[super setObject:obj forKey:key cost:g];
if (!self.config.shouldUseWeakMemoryCache) {
return;
}
if (key && obj) {
// Store weak cache
LOCK(self.weakCacheLock);
// Do the real copy of the key and only let NSMapTable manage the key's lifetime
// Fixes issue #2507 https://github.com/SDWebImage/SDWebImage/issues/2507
[self.weakCache setObject:obj forKey:[[key mutableCopy] copy]];
UNLOCK(self.weakCacheLock);
}
}
接著異步緩存圖片到磁盤中汉形,根據(jù)圖片類型,通過 SDWebImageCodersManager 將圖片解碼為 NSData 類型倍阐,將圖片資源保存到默認的緩存目錄中,文件名為對 key 進行 MD5 后的值逗威。
查圖片
查詢圖片API:
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key; // 內(nèi)存緩存中查
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key; // 磁盤緩存中查
從 SDImageCache 中查圖片:
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDCacheQueryCompletedBlock)doneBlock {
if (!key) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
// First check the in-memory cache...
UIImage *image = [self imageFromMemoryCacheForKey:key];
BOOL shouldQueryMemoryOnly = (image && !(options & SDImageCacheQueryDataWhenInMemory));
if (shouldQueryMemoryOnly) {
if (doneBlock) {
doneBlock(image, nil, SDImageCacheTypeMemory);
}
return nil;
}
NSOperation *operation = [NSOperation new];
void(^queryDiskBlock)(void) = ^{
if (operation.isCancelled) {
// do not call the completion if cancelled
return;
}
@autoreleasepool {
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
UIImage *diskImage;
SDImageCacheType cacheType = SDImageCacheTypeNone;
if (image) {
// the image is from in-memory cache
diskImage = image;
cacheType = SDImageCacheTypeMemory;
} else if (diskData) {
cacheType = SDImageCacheTypeDisk;
// decode image data only if in-memory cache missed
diskImage = [self diskImageForKey:key data:diskData options:options];
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = diskImage.sd_memoryCost;
[self.memCache setObject:diskImage forKey:key cost:cost];
}
}
if (doneBlock) {
if (options & SDImageCacheQueryDiskSync) {
doneBlock(diskImage, diskData, cacheType);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, diskData, cacheType);
});
}
}
}
};
if (options & SDImageCacheQueryDiskSync) {
queryDiskBlock();
} else {
dispatch_async(self.ioQueue, queryDiskBlock);
}
return operation;
}
優(yōu)先從內(nèi)存緩存中查找圖片峰搪,默認內(nèi)存中查到后不會從磁盤中查,內(nèi)存查不到緩存凯旭,則從默認緩存目錄和自定義的查找目錄 customPaths 中遍歷查找概耻。
刪圖片
刪除圖片API:
- (void)removeImageForKey:(nullable NSString *)key withCompletion:(nullable SDWebImageNoParamsBlock)completion;
- (void)removeImageForKey:(nullable NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion;
同步從內(nèi)存緩存中刪除圖片,同時對圖片的弱引用也會刪除罐呼,磁盤圖片則是異步刪除鞠柄,磁盤圖片資源只會從默認緩存目錄中刪除,而不會刪除 customPaths 中的圖片資源嫉柴。
清緩存
清除緩存API:
- (void)clearMemory;
- (void)clearDiskOnCompletion:(nullable SDWebImageNoParamsBlock)completion;
刪除磁盤緩存是異步刪除厌杜。
在 iOS 應用或 TV 應用上,對于一些過期失效的磁盤資源计螺,SDImageCache 會在合適的時機去清除:
- APP 即將銷毀
- APP 已經(jīng)進入后臺
小結
NSCache + NSMapTable 雙重緩存機制可保證 SDWebImage 內(nèi)部緩存在收到內(nèi)存警告而釋放資源后夯尽,還能更快速的從當前 APP 的其他地方獲取到這張圖片。customPaths 傳入的目錄數(shù)組僅用于讀操作登馒,默認所有的 io 操作都在 ioQueue 中串行執(zhí)行匙握。
Downloader
SDWebImageDownloader
圖片下載管理器,管理每個圖片下載操作陈轿,并控制其生命周期圈纺。
- 所有的圖片下載操作都放在 NSOperationQueue 并發(fā)操作隊列 downloadQueue 中秦忿,最大并發(fā)數(shù)為6
- 每個 URL 所對應的下載操作都放在 URLOperations 中,當 URLOperations 不存在該 URL 所對應的下載操作
id<SDWebImageDownloaderOperationInterface>
時蛾娶,才創(chuàng)建新的下載操作 SDWebImageDownloaderOperation 對象小渊,并存入 URLOperations 中,如果已存在 SDWebImageDownloaderOperation 對象operation茫叭,則將 progressBlock 和 completedBlock 保存到 SDWebImageDownloaderOperation 對象的 callbackBlocks 回調(diào)數(shù)組中 - 作為 NSURLSession 和 NSURLSessionDataTask 代理
- 下載操作隊列默認采用 FIFO 先進先出酬屉,可設置為 LIFO 后進先出
- 返回 SDWebImageDownloadToken 對象作為下載操作對象,多次調(diào)用 URL 的下載揍愁,返回的 SDWebImageDownloadToken 不同呐萨,但其屬性 downloadOperation 卻是同一個下載操作對象
SDWebImageDownloaderOperation
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
LOCK(self.callbacksLock);
[self.callbackBlocks addObject:callbacks];
UNLOCK(self.callbacksLock);
return callbacks;
}
- (BOOL)cancel:(nullable id)token {
BOOL shouldCancel = NO;
LOCK(self.callbacksLock);
[self.callbackBlocks removeObjectIdenticalTo:token];
if (self.callbackBlocks.count == 0) {
shouldCancel = YES;
}
UNLOCK(self.callbacksLock);
if (shouldCancel) {
[self cancel];
}
return shouldCancel;
}
處理 URL 對應的具體下載操作,自主管理下載操作狀態(tài)莽囤。callbackBlocks 可變數(shù)組存儲每一個回 SDWebImageDownloaderProgressBlock 和 SDWebImageDownloaderCompletedBlock 回調(diào)谬擦。
取消下載操作的時候,只是將想要取消的操作所對應的 token (即 SDWebImageDownloaderProgressBlock 和 SDWebImageDownloaderCompletedBlock 的鍵值對) 從 callbackBlocks 數(shù)組中移除朽缎。
當可變數(shù)組 callbackBlocks 中的回調(diào)數(shù)為0的時候惨远,才會取消本次下載操作。
設置 option 為 SDWebImageDownloaderProgressiveDownload 可邊下載邊回調(diào)话肖,正常則在圖片下載完成后北秽,在callCompletionBlocksWithImage:imageData:error:finished:
中回調(diào)圖片數(shù)據(jù):
- (void)callCompletionBlocksWithImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
error:(nullable NSError *)error
finished:(BOOL)finished {
NSArray<id> *completionBlocks = [self callbacksForKey:kCompletedCallbackKey];
dispatch_main_async_safe(^{
for (SDWebImageDownloaderCompletedBlock completedBlock in completionBlocks) {
completedBlock(image, imageData, error, finished);
}
});
}
如果存在多個回調(diào),則按照添加的順序回調(diào)的最筒。
小結
對同一個 URL 的多次下載操作贺氓,只會生成一個 operation 對象,只有當 URL 無對應回調(diào)時床蜘,才會真正取消該下載操作
主體 Utils
SDWebImageCombinedOperation
內(nèi)存緩存查詢操作和圖片下載操作的結合體辙培,即包含了 SDWebImage 框架獲取一張圖片的2個主要耗時操作。
-
NSOperation *cacheOperation
耗時的磁盤查詢操作邢锯, -
SDWebImageDownloadToken *downloadToken
網(wǎng)絡圖片下載操作
SDWebImageManager
在我們的平時使用中扬蕊,很少直接操作SDWebImageDownloader和SDImageCache去下載保存圖片,大都是通過SDWebImageManager來管理丹擎,即使通過UIImageView+WebCache等分類加載圖片尾抑,最后也會使用SDWebImageManager來處理。
加載圖片的方法為 loadImageWithURL:options:progress:completed:
判斷 URL 的長度是否大于0鸥鹉;URL 是否在 failedURLs 集合中(存放網(wǎng)絡資源異常的 URL 集合)蛮穿,若在,options 是否包含 SDWebImageRetryFailed
創(chuàng)建一個 SDWebImageCombinedOperation 對象毁渗,保存在 runningOperations 集合中践磅;
從內(nèi)存緩存 SDImageCache 中查詢圖片,并將返回的 NSOperation 賦值給 SDWebImageCombinedOperation 對象的 cacheOperation 屬性
當沒緩存圖片或要求刷新數(shù)據(jù)的時候灸异,通過 SDWebImageDownloader 下載圖片府适,并將返回的 SDWebImageDownloadToken 對象賦值給 SDWebImageCombinedOperation 對象的 downloadToken 屬性
SDWebImagePrefetcher
批預下載管理器羔飞,提前下載一批 URLs 所對應的圖片,每次只能處理一批圖片組檐春。使用的圖片管理器并不是 SDWebImageManager 單例逻淌,而且單獨創(chuàng)建的實例對象;可設置最大并發(fā)數(shù)疟暖,默認為3卡儒。
主要用于提前下載圖片數(shù)據(jù),不依賴于 UI 層俐巴。
SDWebImageTransition
設置圖片的過渡效果
Decoder
圖片解碼骨望,講圖片二進制數(shù)據(jù) NSData 解碼出 UIImage,或?qū)?UIImage 編碼成 NSData
SDWebImageCodersManager
圖片編解碼管理器欣舵,可通過 addCoder:
和 removeCoder:
添加或移除解碼器擎鸠,coders 可變數(shù)組用于存放當前的所有解碼器。默認只有 SDWebImageImageIOCoder 解碼器
- (BOOL)canEncodeToFormat:(SDImageFormat)format {
LOCK(self.codersLock);
NSArray<id<SDWebImageCoder>> *coders = self.coders;
UNLOCK(self.codersLock);
for (id<SDWebImageCoder> coder in coders.reverseObjectEnumerator) {
if ([coder canEncodeToFormat:format]) {
return YES;
}
}
return NO;
}
通過 SDWebImageCodersManager 編解碼圖片的時候缘圈,根據(jù)圖片的二進制數(shù)據(jù)的第一個字節(jié)劣光,獲取圖片格式類型,逆遍歷 coders 中的所有解碼器糟把,直到遇到可以成功解碼該格式的解碼器為止绢涡。
SDWebImageImageIOCoder
支持 PNG, JPEG, TIFF 格式,同時也支持 GIF 格式糊饱,但是只會解碼出第一幀的圖片
SDWebImageGIFCoder
GIF 格式的專用解碼器垂寥,通過 CGImage 遍歷解碼出 GIF 動圖
SDWebImageWebPCoder
WebP 格式的專用解碼器,若想解碼出 WebP 格式的圖片另锋,需要單獨導入 WebP 相關的庫 pod 'SDWebImage/WebP'
小結
在 SDWebImage 4.0.0 之前,是可以直接設置 GIF 動圖的狭归,但是在 4.0.0 之后夭坪,加載的 GIF 動圖只顯示第一幀的圖像。有2種方式顯示網(wǎng)絡上的 GIF 動圖:
- 調(diào)用 SDWebImageCodersManager 的
addCoder:
方法注冊 SDWebImageGIFCoder 解碼器 - 再單獨導入 FLAnimatedImage 庫
pod 'SDWebImage/GIF'
过椎,用 FLAnimatedImageView 替換 UIImageView
第二種方法的性能比第一種高
WebCache Categories
給 UIImageView室梅,UIButton,NSButton疚宇,MKAnnotationView 等常用圖片容易擴充異步圖片加載方法亡鼠。
UIImageView+WebCache 采用 UIView+WebCache 默認的賦值方式(統(tǒng)一當成 UIImageView 處理);而 UIButton+WebCache 則自己實現(xiàn)了 setImageBlock
敷待,MKAnnotationView+WebCache 也是自己實現(xiàn)了 setImageBlock
UIView+WebCache
UIImageView间涵,UIButton,MKAnnotationView 三個類的分類最后也是調(diào)用到了 UIView+WebCache 的sd_internalSetImageWithURL:placeholderImage:options:operationKey:internalSetImageBlock:progress:completed:context:
方法上:
- 根據(jù) operationKey (默認為對象類名)先將上次的加載圖片操作
id<SDWebImageOperation> operation
取消 - 設置占位圖片
- 通過 SDWebImageManager 圖片管理器加載圖片
- 將 SDWebImageManager 返回的
id <SDWebImageOperation> operation
和當前操作符 operationKey 綁定保存在 sd_operationDictionary 可變哈希映射表中
UIImageView+WebCache
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
[self sd_internalSetImageWithURL:url
placeholderImage:placeholder
options:options
operationKey:nil
setImageBlock:nil
progress:progressBlock
completed:completedBlock];
}
對應的操作符 operationKey 為類名榜揖,而setImageBlock
也是 UIView+WebCache 默認以 UIImageView 處理
iOS 應用和 TV 應用
- (void)sd_setAnimationImagesWithURLs:(nonnull NSArray<NSURL *> *)arrayOfURLs {
[self sd_cancelCurrentAnimationImagesLoad];
NSPointerArray *operationsArray = [self sd_animationOperationArray];
[arrayOfURLs enumerateObjectsUsingBlock:^(NSURL *logoImageURL, NSUInteger idx, BOOL * _Nonnull stop) {
__weak __typeof(self) wself = self;
id <SDWebImageOperation> operation = [[SDWebImageManager sharedManager] loadImageWithURL:logoImageURL options:0 progress:nil completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
__strong typeof(wself) sself = wself;
if (!sself) return;
dispatch_main_async_safe(^{
[sself stopAnimating];
if (sself && image) {
NSMutableArray<UIImage *> *currentImages = [[sself animationImages] mutableCopy];
if (!currentImages) {
currentImages = [[NSMutableArray alloc] init];
}
// We know what index objects should be at when they are returned so
// we will put the object at the index, filling any empty indexes
// with the image that was returned too "early". These images will
// be overwritten. (does not require additional sorting datastructure)
while ([currentImages count] < idx) {
[currentImages addObject:image];
}
currentImages[idx] = image;
sself.animationImages = currentImages;
[sself setNeedsLayout];
}
[sself startAnimating];
});
}];
@synchronized (self) {
[operationsArray addPointer:(__bridge void *)(operation)];
}
}];
}
static char animationLoadOperationKey;
// element is weak because operation instance is retained by SDWebImageManager's runningOperations property
// we should use lock to keep thread-safe because these method may not be acessed from main queue
- (NSPointerArray *)sd_animationOperationArray {
@synchronized(self) {
NSPointerArray *operationsArray = objc_getAssociatedObject(self, &animationLoadOperationKey);
if (operationsArray) {
return operationsArray;
}
operationsArray = [NSPointerArray weakObjectsPointerArray];
objc_setAssociatedObject(self, &animationLoadOperationKey, operationsArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return operationsArray;
}
}
- (void)sd_cancelCurrentAnimationImagesLoad {
NSPointerArray *operationsArray = [self sd_animationOperationArray];
if (operationsArray) {
@synchronized (self) {
for (id operation in operationsArray) {
if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) {
[operation cancel];
}
}
operationsArray.count = 0;
}
}
}
對于這兩個平臺的應用勾哩,對 UIImageView 額外新增圖片組的異步加載方法
UIImageView+HighlightedWebCache
- (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
__weak typeof(self)weakSelf = self;
[self sd_internalSetImageWithURL:url
placeholderImage:nil
options:options
operationKey:@"UIImageViewImageOperationHighlighted"
setImageBlock:^(UIImage *image, NSData *imageData) {
weakSelf.highlightedImage = image;
}
progress:progressBlock
completed:completedBlock];
}
對應的操作符 operationKey 為 UIImageViewImageOperationHighlighted抗蠢,而setImageBlock
則是自定義
UIButton+WebCache
- (void)sd_setImageWithURL:(nullable NSURL *)url
forState:(UIControlState)state
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
completed:(nullable SDExternalCompletionBlock)completedBlock {
if (!url) {
[self.sd_imageURLStorage removeObjectForKey:imageURLKeyForState(state)];
} else {
self.sd_imageURLStorage[imageURLKeyForState(state)] = url;
}
__weak typeof(self)weakSelf = self;
[self sd_internalSetImageWithURL:url
placeholderImage:placeholder
options:options
operationKey:imageOperationKeyForState(state)
setImageBlock:^(UIImage *image, NSData *imageData) {
[weakSelf setImage:image forState:state];
}
progress:nil
completed:completedBlock];
}
- (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url
forState:(UIControlState)state
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
completed:(nullable SDExternalCompletionBlock)completedBlock {
if (!url) {
[self.sd_imageURLStorage removeObjectForKey:backgroundImageURLKeyForState(state)];
} else {
self.sd_imageURLStorage[backgroundImageURLKeyForState(state)] = url;
}
__weak typeof(self)weakSelf = self;
[self sd_internalSetImageWithURL:url
placeholderImage:placeholder
options:options
operationKey:backgroundImageOperationKeyForState(state)
setImageBlock:^(UIImage *image, NSData *imageData) {
[weakSelf setBackgroundImage:image forState:state];
}
progress:nil
completed:completedBlock];
}
UIButton 的 image 和 backgroundImage 所對應的 operationKey 根據(jù)不同的狀態(tài) state 而不同,setImageBlock
也不一樣
Other
MKAnnotationView 的做法和 UIImageView 基本一致的思劳,而 NSButton 則是和 UIButton 基本一致迅矛。
End
本文是對 SDWebImage 簡單用法所涉及到的類進行一些簡單的源碼解析。這次的分析是基于 4.3.0 的解析