年也過完了媚狰、決定補(bǔ)完一下入行時(shí)就欠下的債。
參拜一下SDWebImage的源碼诱篷。
并不是說一定要讀如何如何壶唤、只是覺得源碼的閱讀是一種很好的學(xué)習(xí)方式。無論從架構(gòu)還是技術(shù)點(diǎn)方面棕所。
目錄
- 常見疑問(面試大全视粮?)
- 磁盤目錄位于哪里?
- 最大并發(fā)數(shù)橙凳、超時(shí)時(shí)長蕾殴?
- 圖片如何命名?
- 如何識(shí)別圖片類型?
- 所查找到的圖片的來源?
- 所有下載的圖片都將被寫入緩存岛啸?磁盤呢钓觉?何時(shí)緩存的?
- 磁盤緩存的時(shí)長坚踩?清理操作的時(shí)間點(diǎn)荡灾?
- 磁盤清理的原則?
- 下載圖片時(shí)、會(huì)使用緩存協(xié)議么?
- 下載圖片的URL必須是NSURL么批幌?
- 讀取緩存以及讀取磁盤的時(shí)候如何保證線程安全础锐?
- 相關(guān)知識(shí)點(diǎn)
- NS_OPTIONS枚舉與位運(yùn)算
- 內(nèi)聯(lián)函數(shù)
- 準(zhǔn)備工作
- 工作原理
- 業(yè)務(wù)層級(jí)
- 核心代碼(正常讀取下載圖片)
- 最上層:UIView+WebCache
- 邏輯層:SDWebImageManager
- 業(yè)務(wù)層:
- 緩存&&磁盤操作(SDImageCache)
- 下載操作(SDWebImageDownloader)
- 一些啟發(fā)
- 分層的接口API設(shè)計(jì)
- 線程安全
- 內(nèi)聯(lián)函數(shù)
- 精細(xì)的緩存管理原則
- 回調(diào)設(shè)計(jì)
常見疑問(面試大全?)
雖然我更推薦閱讀源碼荧缘、可如果實(shí)在沒時(shí)間皆警。這一段只要花費(fèi)幾分鐘。
我還是比較喜歡把干貨放在前面截粗、方便伸手黨(比如我)信姓。
不過也不能保證涵蓋全部問題、歡迎留言绸罗。
-
磁盤目錄位于哪里意推?
緩存在磁盤沙盒目錄下 Library/Caches
二級(jí)目錄為~/Library/Caches/default/com.hackemist.SDWebImageCache.default
- (instancetype)init {
return [self initWithNamespace:@"default"];
// ~Library/Caches/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]
// Init the disk cache
if (directory != nil) {
_diskCachePath = [directory stringByAppendingPathComponent:fullNamespace];
} else {
NSString *path = [self makeDiskCachePath:ns];
_diskCachePath = path;
}
// _diskCachePath = ~/Library/Caches/default/com.hackemist.SDWebImageCache.default
}
你也可以通過[[SDImageCache sharedImageCache] addReadOnlyCachePath:bundledPath];
來自定義一個(gè)路徑。
但這個(gè)路徑不會(huì)被存儲(chǔ)使用珊蟀、是給開發(fā)者自定義預(yù)裝圖片的路徑菊值。
-
最大并發(fā)數(shù)、超時(shí)時(shí)長育灸?
_downloadQueue = [NSOperationQueue new];
_downloadQueue.maxConcurrentOperationCount = 6;
_downloadTimeout = 15.0;
-
圖片如何命名腻窒?
這里寫入緩存和寫入磁盤是不同的。
寫入緩存時(shí)描扯、直接用圖片url作為key
//寫入緩存
NSUInteger cost = SDCacheCostForImage(image);
[self.memCache setObject:image forKey:key cost:cost];
寫入磁盤時(shí)、用url的MD5編碼作為key趟薄≌莱希可以防止文件名過長
- (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);
NSURL *keyURL = [NSURL URLWithString:key];
NSString *ext = keyURL ? keyURL.pathExtension : key.pathExtension;
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], ext.length == 0 ? @"" : [NSString stringWithFormat:@".%@", ext]];
return filename;
//key == https://gss2.bdstatic.com/-fo3dSag_xI4khGkpoWK1HF6hhy/baike/c0%3Dbaike80%2C5%2C5%2C80%2C26/sign=034361ab922397ddc274905638ebd9d2/d31b0ef41bd5ad64dddebb.jpg;
//filename == f029945f95894e152771806785bc4f18.jpg;
}
-
如何識(shí)別圖片類型?
通過NSData數(shù)據(jù)的第一個(gè)字符進(jìn)行判斷。
+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data {
if (!data) {
return SDImageFormatUndefined;
}
// File signatures table: http://www.garykessler.net/library/file_sigs.html
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: {
if (data.length >= 12) {
//RIFF....WEBP
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
return SDImageFormatWebP;
}
}
break;
}
case 0x00: {
if (data.length >= 12) {
//....ftypheic ....ftypheix ....ftyphevc ....ftyphevx
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(4, 8)] encoding:NSASCIIStringEncoding];
if ([testString isEqualToString:@"ftypheic"]
|| [testString isEqualToString:@"ftypheix"]
|| [testString isEqualToString:@"ftyphevc"]
|| [testString isEqualToString:@"ftyphevx"]) {
return SDImageFormatHEIC;
}
}
break;
}
}
return SDImageFormatUndefined;
}
-
所查找到的圖片的來源?
typedef NS_ENUM(NSInteger, SDImageCacheType) {
/**
* 從網(wǎng)上下載
*/
SDImageCacheTypeNone,
/**
* 從磁盤獲得
*/
SDImageCacheTypeDisk,
/**
* 從內(nèi)存獲得
*/
SDImageCacheTypeMemory
};
-
所有下載的圖片都將被寫入緩存杭煎?磁盤呢恩够?何時(shí)緩存的?
磁盤不是強(qiáng)制寫入羡铲。從枚舉SDWebImageOptions
可見
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
/**
* 禁用磁盤緩存
*/
SDWebImageCacheMemoryOnly = 1 << 2,
}
而Memory緩存應(yīng)該是必須寫入的(因?yàn)槲也]找到哪里可以禁止)蜂桶。
緩存的時(shí)間點(diǎn)、有兩個(gè)(開發(fā)者也可以主動(dòng)緩存)也切、且都是由SDWebImageManager
進(jìn)行扑媚。
其一是下載成功后、自動(dòng)保存雷恃〗桑或者開發(fā)者通過代理處理圖片并返回后緩存
- (nullable UIImage *)imageManager:(nonnull SDWebImageManager *)imageManager transformDownloadedImage:(nullable UIImage *)image withURL:(nullable NSURL *)imageURL;
=========>>SDWebImageManager
//獲取轉(zhuǎn)換用戶后的圖片
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
//用戶處理成功
if (transformedImage && finished) {
BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
//用戶處理的后若未生成新的圖片、則保存下載的二進(jìn)制文件倒槐。
//不然則由imageCache內(nèi)部生成二進(jìn)制文件保存
[self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil];
}
其二是當(dāng)緩存中沒有旬痹、但是從硬盤中查詢到了圖片。
@autoreleasepool {
//搜索硬盤
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
UIImage *diskImage = [self diskImageForKey:key];
//緩存到內(nèi)存、默認(rèn)為YES
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
//使用NSChache緩存两残。
[self.memCache setObject:diskImage forKey:key cost:cost];
}
if (doneBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
});
}
}
-
磁盤緩存的時(shí)長永毅?清理操作的時(shí)間點(diǎn)?
默認(rèn)為一周
static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week
能夠以時(shí)間清除磁盤的方法為
- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock;
調(diào)用的時(shí)機(jī)為
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(deleteOldFiles)
name:UIApplicationWillTerminateNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(backgroundDeleteOldFiles)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
也就是當(dāng)程序退出到后臺(tái)人弓、或者被殺死的時(shí)候沼死。
這里、還有另外一個(gè)點(diǎn)票从。
Long-Running Task任務(wù)
- (void)backgroundDeleteOldFiles {
Class UIApplicationClass = NSClassFromString(@"UIApplication");
if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
return;
}
UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)];
//后臺(tái)任務(wù)標(biāo)識(shí)--注冊(cè)一個(gè)后臺(tái)任務(wù)
__block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
//超時(shí)(大概150秒漫雕?)自動(dòng)結(jié)束后臺(tái)任務(wù)
//結(jié)束后臺(tái)任務(wù)
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
[self deleteOldFilesWithCompletionBlock:^{
//結(jié)束后臺(tái)任務(wù)
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
}
正常程序在進(jìn)入后臺(tái)后、雖然可以繼續(xù)執(zhí)行任務(wù)峰鄙。但是在時(shí)間很短內(nèi)就會(huì)被掛起待機(jī)浸间。
Long-Running可以讓系統(tǒng)為app再多分配一些時(shí)間來處理一些耗時(shí)任務(wù)。
-
磁盤清理的原則吟榴?
首先魁蒜、通過時(shí)間進(jìn)行清理。(最后修改時(shí)間>一周)
然后吩翻、根據(jù)占據(jù)內(nèi)存大小進(jìn)行清理兜看。(如果占據(jù)內(nèi)存大于上限、則按時(shí)間排序狭瞎、刪除到上限的1/2细移。)
這里我并沒有看到使用頻率優(yōu)先級(jí)判斷、所以應(yīng)該是沒有熊锭。
- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock {
//異步清理超時(shí)圖片
dispatch_async(self.ioQueue, ^{
//獲取磁盤目錄
NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
//NSURLIsDirectoryKey 判斷是否為目錄
//NSURLContentModificationDateKey 判斷最后修改時(shí)間
//NSURLTotalFileAllocatedSizeKey 判斷文件大小
NSArray<NSString *> *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];
//模具器--遍歷磁盤路徑下的文件
NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
includingPropertiesForKeys:resourceKeys
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:NULL];
//計(jì)算一周前(需要釋放)弧轧、的時(shí)間
NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.config.maxCacheAge];
//保存緩存文件Dic
NSMutableDictionary<NSURL *, NSDictionary<NSString *, id> *> *cacheFiles = [NSMutableDictionary dictionary];
//緩存總大小
NSUInteger currentCacheSize = 0;
//需要?jiǎng)h除的url路徑
NSMutableArray<NSURL *> *urlsToDelete = [[NSMutableArray alloc] init];
//遍歷磁盤文件枚舉器
for (NSURL *fileURL in fileEnumerator) {
NSError *error;
//獲取每個(gè)文件所對(duì)應(yīng)的三個(gè)參數(shù)(resourceKeys)
NSDictionary<NSString *, id> *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:&error];
// Skip directories and errors.
if (error || !resourceValues || [resourceValues[NSURLIsDirectoryKey] boolValue]) {
//如果是文件夾則跳過
continue;
}
// Remove files that are older than the expiration date;
NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];
if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
//如果時(shí)間超過指定日期、加入刪除數(shù)組碗殷。跳過
[urlsToDelete addObject:fileURL];
continue;
}
//獲取文件大小精绎、并且把路徑與大小存入字典。
// Store a reference to this file and account for its total size.
NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
currentCacheSize += totalAllocatedSize.unsignedIntegerValue;
cacheFiles[fileURL] = resourceValues;
}
//遍歷刪除文件
for (NSURL *fileURL in urlsToDelete) {
[_fileManager removeItemAtURL:fileURL error:nil];
}
//如果剩余文件大小仍超過閾值
//優(yōu)先刪除最老的文件
if (self.config.maxCacheSize > 0 && currentCacheSize > self.config.maxCacheSize) {
// Target half of our maximum cache size for this cleanup pass.
const NSUInteger desiredCacheSize = self.config.maxCacheSize / 2;
// 將剩余的文件按修改時(shí)間排序
NSArray<NSURL *> *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
usingComparator:^NSComparisonResult(id obj1, id obj2) {
return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];
}];
// 刪除文件
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;
}
}
}
}
//回調(diào)給主線程
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
}
-
下載圖片時(shí)锌妻、會(huì)使用網(wǎng)絡(luò)協(xié)議緩存邏輯么?
默認(rèn)情況下不會(huì)代乃、由以下代碼可見。
NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
cachePolicy:cachePolicy
timeoutInterval:timeoutInterval];
除非將options
配置成SDWebImageDownloaderUseNSURLCache
仿粹、否則每次都會(huì)從原地址重新下載搁吓、而不是用網(wǎng)絡(luò)協(xié)議的緩存邏輯。
-
下載圖片的URL必須是NSURL么吭历?
不是擎浴、在SDWebImageManager
中有過容錯(cuò)處理。所以即便你傳入一個(gè)字符串毒涧、依舊可以正確的查找贮预。
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}
但是由于API暴露出的是(nullable NSURL *)
贝室、如果你傳入字符串、會(huì)有黃色警告
-
讀取緩存以及讀取磁盤的時(shí)候如何保證線程安全仿吞?
- 讀取緩存
讀取緩存的時(shí)候是在主線程進(jìn)行滑频。由于使用NSCache進(jìn)行存儲(chǔ)、所以不需要擔(dān)心單個(gè)value對(duì)象的線程安全唤冈。 - 讀取磁盤
磁盤的讀取雖然創(chuàng)建了一個(gè)NSOperation對(duì)象峡迷、但據(jù)我所見這個(gè)對(duì)象只是用來標(biāo)記該操作是否被取消、以及取消之后不再讀取磁盤文件的作用你虹。
真正的磁盤緩存是在另一個(gè)IO專屬線程
中的一個(gè)串行隊(duì)列
下進(jìn)行的绘搞。
如果你搜索self.ioQueue
還能發(fā)現(xiàn)、不只是讀取磁盤內(nèi)容傅物。
包括刪除夯辖、寫入等所有磁盤內(nèi)容都是在這個(gè)IO線程進(jìn)行、以保證線程安全董饰。
但計(jì)算大小蒿褂、獲取文件總數(shù)等操作。則是在主線程進(jìn)行卒暂。
_ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);
==========>>>>><<<<<<===========
NSOperation *operation = [NSOperation new];
dispatch_async(self.ioQueue, ^{
if (operation.isCancelled) {
// do not call the completion if cancelled
return;
}
@autoreleasepool {
//搜索硬盤
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
UIImage *diskImage = [self diskImageForKey:key];
//緩存到內(nèi)存啄栓、默認(rèn)為YES
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
//使用NSChache緩存。
[self.memCache setObject:diskImage forKey:key cost:cost];
}
if (doneBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
});
}
}
});
return operation;
相關(guān)知識(shí)點(diǎn)
如果對(duì)一些知識(shí)點(diǎn)不了解也祠、可能對(duì)代碼理解造成困擾昙楚。列舉一下。
-
NS_OPTIONS枚舉與位運(yùn)算
上文中的SDWebImageOptions便是一個(gè)位移枚舉
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
SDWebImageRetryFailed = 1 << 0,
SDWebImageLowPriority = 1 << 1,
SDWebImageCacheMemoryOnly = 1 << 2,
SDWebImageProgressiveDownload = 1 << 3,
SDWebImageRefreshCached = 1 << 4,
SDWebImageContinueInBackground = 1 << 5,
SDWebImageHandleCookies = 1 << 6,
SDWebImageAllowInvalidSSLCertificates = 1 << 7,
SDWebImageHighPriority = 1 << 8,
SDWebImageDelayPlaceholder = 1 << 9,
SDWebImageTransformAnimatedImage = 1 << 10,
SDWebImageAvoidAutoSetImage = 1 << 11,
SDWebImageScaleDownLargeImages = 1 << 12
};
和我們普通用的枚舉
typedef NS_ENUM(NSInteger, SDImageCacheType) {
SDImageCacheTypeNone,
SDImageCacheTypeDisk,
SDImageCacheTypeMemory
};
從表面看有兩點(diǎn)不同:
- 枚舉聲明:NS_ENUM&& NS_OPTIONS
其實(shí)從定義的效果上來講诈嘿、二者作用相同堪旧。
更多的是語義化的角度。前者是普通枚舉
永淌、后者是位移枚舉
崎场。 - 枚舉中的位運(yùn)算符號(hào)
<<
.
位運(yùn)算中佩耳、有三種基本運(yùn)算符號(hào).
按位與"&"
只有對(duì)應(yīng)的兩個(gè)二進(jìn)位均為1時(shí)遂蛀,結(jié)果位才為1,否則為0
比如9&5干厚,其實(shí)就是1001&0101=0001李滴,因此9&5=1>二進(jìn)制中,與1相&就保持原位蛮瞄,與0相&就為0
按位或"|"
只要對(duì)應(yīng)的二個(gè)二進(jìn)位有一個(gè)為1時(shí)所坯,結(jié)果位就為1,否則為0挂捅。
比如9|5芹助,其實(shí)就是1001|0101=1101,因此9|5=13
左移"<<"
把整數(shù)a的各二進(jìn)位全部左移n位,高位丟棄状土,低位補(bǔ)0无蜂。左移n位其實(shí)就是乘以2的n次方。
例如1<<2 就是0001左移2為0100蒙谓,因此1<<2=4
于是斥季、在使用位移枚舉的時(shí)候、我們就有了這種寫法:
options:SDWebImageRetryFailed | SDWebImageCacheMemoryOnly];
上面的意思是累驮。這個(gè)操作是如果失敗了需要重試酣倾、并且只寫入緩存。
其中 options=SDWebImageRetryFailed | SDWebImageCacheMemoryOnly
也就是0b00000001| 0b00000100 = 0b00000101
十進(jìn)制中 = 5.
在內(nèi)部判斷時(shí)候就有了如下寫法:
//是否磁盤緩存
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
等價(jià)于 0101 & 0100 = 0100
結(jié)果為真谤专。
倘若
BOOL lowPriority = !(options & SDWebImageLowPriority);
等價(jià)于 0101 & 0010 = 0000
結(jié)果為假躁锡。
-
內(nèi)聯(lián)函數(shù)
在寫入緩存時(shí)、出現(xiàn)了這樣一行代碼
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
其中SDCacheCostForImage指向一個(gè)靜態(tài)內(nèi)聯(lián)函數(shù)
FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
#if SD_MAC
return image.size.height * image.size.width;
#elif SD_UIKIT || SD_WATCH
return image.size.height * image.size.width * image.scale * image.scale;
#endif
}
其中FOUNDATION_STATIC_INLINE作為宏指向static inline毒租、所以也等價(jià)于
static __inline__ NSUInteger SDCacheCostForImage(UIImage *image) {
#if SD_MAC
return image.size.height * image.size.width;
#elif SD_UIKIT || SD_WATCH
return image.size.height * image.size.width * image.scale * image.scale;
#endif
}
用宏寫方法稚铣、我們都用過。但是表達(dá)式形式的宏定義有一定的弊端墅垮。(比如參數(shù)檢查惕医、越界等等)。
內(nèi)聯(lián)函數(shù)完全可以取代表達(dá)式形式的宏定義算色。
順便談?wù)劄槭裁匆脙?nèi)聯(lián)函數(shù)吧抬伺。
- 效率來看
- 函數(shù)之間調(diào)用,是內(nèi)存地址之間的調(diào)用灾梦、當(dāng)函數(shù)調(diào)用完畢之后還會(huì)返回原來函數(shù)執(zhí)行的地址虐先。函數(shù)調(diào)用將會(huì)有時(shí)間開銷。
- 內(nèi)聯(lián)函數(shù)在匯編中沒有call語句肉康。取消了函數(shù)的參數(shù)壓棧
- 相比表達(dá)式形式的宏定義
- 需要預(yù)編譯.因?yàn)閕nline內(nèi)聯(lián)函數(shù)也是函數(shù)募强、不需要預(yù)編譯。
- 調(diào)用時(shí)候會(huì)首先檢查它的參數(shù)的類型萧福、保證調(diào)用正確拉鹃。
- 可以使用所在類的保護(hù)成員及私有成員。
需要注意的是
- 內(nèi)聯(lián)函數(shù)中盡量不要使用諸如循環(huán)語句等大量代碼鲫忍、可能會(huì)導(dǎo)致編譯器放棄內(nèi)聯(lián)動(dòng)作膏燕。
- 內(nèi)聯(lián)函數(shù)的定義須在調(diào)用之前。
準(zhǔn)備工作
隨手下載了一個(gè)最新的 (4.2.3)
GitHub
PODS:
- SDWebImage (4.2.3):
- SDWebImage/Core (= 4.2.3)
- SDWebImage/Core (4.2.3)
DEPENDENCIES:
- SDWebImage
SPEC CHECKSUMS:
SDWebImage: 791bb72962b3492327ddcac4b1880bd1b5458431
PODFILE CHECKSUM: 7fbc0b76fb4d0b0b2afa7d3a90b7bd68dea25abb
COCOAPODS: 1.3.1
工作原理
引用GitHub上一個(gè)導(dǎo)圖
- 1悟民、外部API入口坝辫。
通過UIImageView+WebCache
的sd_setImageWithURL
方法(等)作為入口來加載圖片。 - 2射亏、內(nèi)部API匯總近忙。
通過UIView+WebCache
的'sd_internalSetImageWithURL'對(duì)UIImageView竭业、UIButton 、MKAnnotationView中圖片的下載請(qǐng)求進(jìn)行匯總及舍。 - 3永品、開始加載圖片。
通過SDWebImageManager
的loadImageWithURL
對(duì)圖片進(jìn)行加載击纬。 - 4鼎姐、查找本地
通過SDImageCache
的queryCacheOperationForKey
查找緩存中是否存在圖片。如果不存在再通過diskImageDataBySearchingAllPathsForKey
進(jìn)行磁盤搜索更振。 - 5炕桨、返回本地圖片給
SDWebImageManager
- 6、下載圖片
如果本地查詢不到對(duì)應(yīng)圖片肯腕、則通過SDImageDownloader
的downloadImage
進(jìn)行圖片下載献宫。 - 7、下載完畢返回圖片給
SDWebImageManager
- 8实撒、由
UIView+WebCache
通過storeImage
將下載圖片保存本地 - 9姊途、返回圖片給
UIView+WebCache
- 10、設(shè)置圖片
其中知态。
業(yè)務(wù)層級(jí)
- 整個(gè)架構(gòu)簡單分為三層捷兰。
最上層:
負(fù)責(zé)業(yè)務(wù)的接入、圖片的插入
#import "UIImageView+WebCache.h"
#import "UIButton+WebCache.h"
#import "UIImageView+HighlightedWebCache.h"
//以及其匯總的
#import "UIView+WebCache.h"
邏輯層
負(fù)責(zé)不同類型業(yè)務(wù)的分發(fā)负敏。
讀取(或?qū)懭?緩存(或磁盤)贡茅、下載等具體邏輯處理。
#import "SDWebImageManager.h"
業(yè)務(wù)層
負(fù)責(zé)具體業(yè)務(wù)的實(shí)現(xiàn)
//緩存&&磁盤操作
#import "SDImageCache.h"
//下載操作
#import "SDWebImageDownloader.h"
當(dāng)然其做、還有其他的工具類顶考。但主要的、就是上面幾個(gè)妖泄。
核心代碼(正常讀取下載圖片)
-
最上層:UIView+WebCache
所有的代碼最終都會(huì)匯總到
#import "UIView+WebCache.h"
/**
* @param url 圖片地址鏈接
* @param placeholder 占位圖
* @param options 下載圖片的枚舉驹沿。包括優(yōu)先級(jí)、是否寫入硬盤等
* @param operationKey 一個(gè)記錄當(dāng)前對(duì)象正在加載操作的key蹈胡、保證只有最新的操作在進(jìn)行渊季、默認(rèn)為類名。
所以如果你想下載多個(gè)圖片并且都展示一下审残、可以嘗試自定義幾個(gè)operationKey來操作梭域。(我猜)
* @param setImageBlock 給開發(fā)者自定義set圖片的callback
* @param progressBlock 下載進(jìn)度callback
* @param completedBlock 下載完成的callback(sd已經(jīng)給你set好了斑举、只是會(huì)把圖片給你罷了)
* @param context 一些額外的上下文字典搅轿。比如你可以搞一個(gè)專屬的imageManager進(jìn)來干活。
*/
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock
context:(nullable NSDictionary *)context {
//以當(dāng)前實(shí)例的class作為OperationKey
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
//清除當(dāng)前OperationKey下正在進(jìn)行的操作富玷。節(jié)省無用功
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
//給對(duì)象實(shí)例綁定imageURLKey = url
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//是否先加載占位圖
if (!(options & SDWebImageDelayPlaceholder)) {
if ([context valueForKey:SDWebImageInternalSetImageGroupKey]) {
dispatch_group_t group = [context valueForKey:SDWebImageInternalSetImageGroupKey];
dispatch_group_enter(group);
}
//到主線城更新UI
dispatch_main_async_safe(^{
//set 占位圖
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
}
if (url) {
// 小菊花
if ([self sd_showActivityIndicatorView]) {
[self sd_addActivityIndicator];
}
// 允許開發(fā)者指定一個(gè)manager來進(jìn)行操作
SDWebImageManager *manager;
if ([context valueForKey:SDWebImageExternalCustomManagerKey]) {
manager = (SDWebImageManager *)[context valueForKey:SDWebImageExternalCustomManagerKey];
} else {
manager = [SDWebImageManager sharedManager];
}
__weak __typeof(self)wself = self;
id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
//圖片下載||讀取完成
__strong __typeof (wself) sself = wself;
//小菊花
[sself sd_removeActivityIndicator];
if (!sself) { return; }
BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
//是否不插入圖片
//1璧坟、有圖片既穆、但是主動(dòng)配置
//2、沒圖片雀鹃、設(shè)置了延遲加載占位圖
BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
(!image && !(options & SDWebImageDelayPlaceholder)));
SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
//
if (!sself) { return; }
if (!shouldNotSetImage) {
[sself sd_setNeedsLayout];
}
if (completedBlock && shouldCallCompletedBlock) {
//操作完成的回調(diào)
completedBlock(image, error, cacheType, url);
}
};
// case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set
// OR
// case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
if (shouldNotSetImage) {
//如果不顯示圖片幻工、直接回調(diào)。
dispatch_main_async_safe(callCompletedBlockClojure);
return;
}
/**自動(dòng)插入圖片***/
UIImage *targetImage = nil;
NSData *targetData = nil;
if (image) {
// case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
targetImage = image;
targetData = data;
} else if (options & SDWebImageDelayPlaceholder) {
// case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
targetImage = placeholder;
targetData = nil;
}
if ([context valueForKey:SDWebImageInternalSetImageGroupKey]) {
dispatch_group_t group = [context valueForKey:SDWebImageInternalSetImageGroupKey];
dispatch_group_enter(group);
dispatch_main_async_safe(^{
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
// ensure completion block is called after custom setImage process finish
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
callCompletedBlockClojure();
});
} else {
dispatch_main_async_safe(^{
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock];
callCompletedBlockClojure();
});
}
}];
//在讀取圖片之前黎茎。向正在進(jìn)行加載的HashMap中加入當(dāng)前operation
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} else {
dispatch_main_async_safe(^{
[self sd_removeActivityIndicator];
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
}
}
一個(gè)簡單的流程圖
-
邏輯層:SDWebImageManager
SDWebImage中最核心的類囊颅、調(diào)度這圖片的下載(SDWebImageDownloader
)以及緩存(SDImageCache
)。
此外傅瞻、SDWebImageManager
并不依托于UIView+WebCache
踢代、完全可以單獨(dú)使用。
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
//所以嗅骄、我們并不需要在外部把字符串變?yōu)镹SURL胳挎。
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}
//下載操作的對(duì)象
__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;
BOOL isFailedUrl = NO;
if (url) {
@synchronized (self.failedURLs) {
//線程安全
isFailedUrl = [self.failedURLs containsObject:url];
}
}
//url為空 || (未設(shè)置失敗重試 && 這個(gè)url已經(jīng)失敗過)
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
//發(fā)出一個(gè)獲取失敗的回調(diào)
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url];
return operation;
}
//將操作添加到正在進(jìn)行的操作數(shù)池
@synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];
}
//默認(rèn)就是url作為key、也可以自定義mananger的相關(guān)block
NSString *key = [self cacheKeyForURL:url];
//通過key溺森、查找本地圖片
operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
if (operation.isCancelled) {
//操作被取消慕爬、移除操作池
[self safelyRemoveOperationFromRunning:operation];
return;
}
//本地沒有圖片 || 刷新緩存
if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
//有本地圖片。但需要被刷新
if (cachedImage && options & SDWebImageRefreshCached) {
//先回調(diào)出去本地圖片屏积。再繼續(xù)下載操作
[self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
}
//下面是根據(jù)調(diào)用者傳進(jìn)來的option医窿,來匹配設(shè)置了哪些,就給downloaderOptions賦值哪些option
SDWebImageDownloaderOptions downloaderOptions = 0;
if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
if (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages;
if (cachedImage && options & SDWebImageRefreshCached) {
// force progressive off if image already cached but forced refreshing
downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
// ignore image read from NSURLCache if image if cached but force refreshing
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}
//下載圖片
SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (!strongOperation || strongOperation.isCancelled) {
// Do nothing if the operation was cancelled
// See #699 for more details
// if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data
} else if (error) {
[self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url];
if ( error.code != NSURLErrorNotConnectedToInternet
&& error.code != NSURLErrorCancelled
&& error.code != NSURLErrorTimedOut
&& error.code != NSURLErrorInternationalRoamingOff
&& error.code != NSURLErrorDataNotAllowed
&& error.code != NSURLErrorCannotFindHost
&& error.code != NSURLErrorCannotConnectToHost
&& error.code != NSURLErrorNetworkConnectionLost) {
@synchronized (self.failedURLs) {
//失敗記錄
[self.failedURLs addObject:url];
}
}
}
else {
if ((options & SDWebImageRetryFailed)) {
//失敗重新下載
@synchronized (self.failedURLs) {
//從失敗記錄移除
[self.failedURLs removeObject:url];
}
}
//是否磁盤緩存
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
if (self != [SDWebImageManager sharedManager] && self.cacheKeyFilter && downloadedImage) {
//縮放
downloadedImage = [self scaledImageForKey:key image:downloadedImage];
}
if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
//是否需要轉(zhuǎn)換圖片
//成功下載圖片炊林、自定義實(shí)現(xiàn)了圖片處理的代理
} else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
//獲取轉(zhuǎn)換用戶后的圖片
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
//用戶處理成功
if (transformedImage && finished) {
BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
//用戶處理的后若未生成新的圖片留搔、則保存下載的二進(jìn)制文件。
//不然則由imageCache內(nèi)部生成二進(jìn)制文件保存
[self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil];
}
//回調(diào)
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
});
} else {
//下載成功且未自定義代理--默認(rèn)保存
if (downloadedImage && finished) {
[self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
}
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
}
}
if (finished) {
[self safelyRemoveOperationFromRunning:strongOperation];
}
}];
@synchronized(operation) {
operation.cancelBlock = ^{
[self.imageDownloader cancel:subOperationToken];
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self safelyRemoveOperationFromRunning:strongOperation];
};
}
} else if (cachedImage) {
//本地有圖片--回調(diào)铛铁、關(guān)閉當(dāng)前操作
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
[self safelyRemoveOperationFromRunning:operation];
} else {
//本地沒有隔显、也不下載--回調(diào)、關(guān)閉當(dāng)前操作
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
[self safelyRemoveOperationFromRunning:operation];
}
}];
return operation;
}
-
業(yè)務(wù)層:
緩存&&磁盤操作(SDImageCache)
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key 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];
if (image) {
NSData *diskData = nil;
if (image.images) {
diskData = [self diskImageDataBySearchingAllPathsForKey:key];
}
if (doneBlock) {
doneBlock(image, diskData, SDImageCacheTypeMemory);
}
return nil;
}
NSOperation *operation = [NSOperation new];
dispatch_async(self.ioQueue, ^{
if (operation.isCancelled) {
// do not call the completion if cancelled
return;
}
@autoreleasepool {
//搜索硬盤
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
UIImage *diskImage = [self diskImageForKey:key];
//緩存到內(nèi)存饵逐、默認(rèn)為YES
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
//使用NSChache緩存括眠。
[self.memCache setObject:diskImage forKey:key cost:cost];
}
if (doneBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
});
}
}
});
return operation;
}
//查詢緩存
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key {
//self.memCache 為NSCache實(shí)例
return [self.memCache objectForKey:key];
}
//查詢磁盤
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key {
NSData *data = [self diskImageDataBySearchingAllPathsForKey:key];
if (data) {
//圖片解碼、調(diào)整方向
UIImage *image = [[SDWebImageCodersManager sharedInstance] decodedImageWithData:data];
//調(diào)整圖片縮放比例 @2x/@3x
image = [self scaledImageForKey:key image:image];
//壓縮圖片
if (self.config.shouldDecompressImages) {
image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&data options:@{SDWebImageCoderScaleDownLargeImagesKey: @(NO)}];
}
return image;
} else {
return nil;
}
}
//寫入緩存 && 磁盤
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
if (!image || !key) {
if (completionBlock) {
completionBlock();
}
return;
}
// if memory cache is enabled
if (self.config.shouldCacheImagesInMemory) {
//寫入緩存
NSUInteger cost = SDCacheCostForImage(image);
[self.memCache setObject:image forKey:key cost:cost];
}
if (toDisk) {
//寫入磁盤
dispatch_async(self.ioQueue, ^{
@autoreleasepool {
NSData *data = imageData;
if (!data && image) {
// If we do not have any data to detect image format, check whether it contains alpha channel to use PNG or JPEG format
SDImageFormat format;
if (SDCGImageRefContainsAlpha(image.CGImage)) {
format = SDImageFormatPNG;
} else {
format = SDImageFormatJPEG;
}
data = [[SDWebImageCodersManager sharedInstance] encodedDataWithImage:image format:format];
}
[self storeImageDataToDisk:data forKey:key];
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
} else {
if (completionBlock) {
completionBlock();
}
}
}
//正式寫入磁盤
- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
if (!imageData || !key) {
return;
}
[self checkIfQueueIsIOQueue];
//如果文件中不存在磁盤緩存路徑 則創(chuàng)建
if (![_fileManager fileExistsAtPath:_diskCachePath]) {
[_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
}
// get cache Path for image key 得到該key的緩存路徑
NSString *cachePathForKey = [self defaultCachePathForKey:key];
// transform to NSUrl 將緩存路徑轉(zhuǎn)化為url
NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
//將imageData存儲(chǔ)起來
[_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil];
// disable iCloud backup 如果調(diào)用者關(guān)閉icloud 關(guān)閉iCloud備份
if (self.config.shouldDisableiCloud) {
[fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
}
}
由于此處只歸納正常讀取下載流程的代碼倍权、所以其余關(guān)于圖片過期&&釋放流程的代碼沒有列出掷豺。后面會(huì)逐一進(jìn)行歸納。
下載操作(SDWebImageDownloader)
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
__weak SDWebImageDownloader *wself = self;
return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^SDWebImageDownloaderOperation *{
//創(chuàng)建下載operation
__strong __typeof (wself) sself = wself;
//超時(shí)時(shí)間
NSTimeInterval timeoutInterval = sself.downloadTimeout;
if (timeoutInterval == 0.0) {
timeoutInterval = 15.0;
}
// In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
//創(chuàng)建下載策略
//SDWebImageDownloaderUseNSURLCache 則使用 NSURLRequestUseProtocolCachePolicy 緩存協(xié)議
//默認(rèn)NSURLRequestReloadIgnoringLocalCacheData從原地址重新下載
NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
//創(chuàng)建下載請(qǐng)求
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
cachePolicy:cachePolicy
timeoutInterval:timeoutInterval];
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
request.HTTPShouldUsePipelining = YES;
if (sself.headersFilter) {
request.allHTTPHeaderFields = sself.headersFilter(url, [sself.HTTPHeaders copy]);
}
else {
//默認(rèn) image/*;q=0.8
request.allHTTPHeaderFields = sself.HTTPHeaders;
}
//創(chuàng)建下載操作
SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options];
//是否解壓
operation.shouldDecompressImages = sself.shouldDecompressImages;
//證書
if (sself.urlCredential) {
operation.credential = sself.urlCredential;
} else if (sself.username && sself.password) {
//默認(rèn) 賬號(hào)密碼為空的通用證書
operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession];
}
//優(yōu)先級(jí)薄声。默認(rèn)都不是
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
}
//向下載隊(duì)列 NSOperationQueue 中 添加本次下載操作
[sself.downloadQueue addOperation:operation];
//設(shè)置下載的順序 是按照隊(duì)列還是棧
if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
// Emulate LIFO execution order by systematically adding new operations as last operation's dependency
[sself.lastAddedOperation addDependency:operation];
sself.lastAddedOperation = operation;
}
return operation;
}];
}
//通過progressBlock&&completedBlock以及Url和SDWebImageDownloaderOperation對(duì)token進(jìn)行包裝
- (nullable SDWebImageDownloadToken *)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock
completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
forURL:(nullable NSURL *)url
createCallback:(SDWebImageDownloaderOperation *(^)(void))createCallback {
// The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.
if (url == nil) {
if (completedBlock != nil) {
completedBlock(nil, nil, nil, NO);
}
return nil;
}
__block SDWebImageDownloadToken *token = nil;
dispatch_barrier_sync(self.barrierQueue, ^{
SDWebImageDownloaderOperation *operation = self.URLOperations[url];
if (!operation) {
operation = createCallback();
//將url作為key当船、對(duì)應(yīng)的下載操作operation作為value保存。
self.URLOperations[url] = operation;
__weak SDWebImageDownloaderOperation *woperation = operation;
operation.completionBlock = ^{
dispatch_barrier_sync(self.barrierQueue, ^{
SDWebImageDownloaderOperation *soperation = woperation;
if (!soperation) return;
if (self.URLOperations[url] == soperation) {
//下載完成默辨、移除操作
[self.URLOperations removeObjectForKey:url];
};
});
};
}
//將成progressBlock以及completedBlock組裝成SDCallbacksDictionary.
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
//生成下載任務(wù)標(biāo)識(shí)德频。用于manager將來定位對(duì)應(yīng)操作用
token = [SDWebImageDownloadToken new];
token.url = url;
token.downloadOperationCancelToken = downloadOperationCancelToken;
});
return token;
}
SDWebImageDownloaderOperation是具體下載操作、設(shè)計(jì)很多網(wǎng)絡(luò)層的東西缩幸。將來可以單獨(dú)開一篇壹置、結(jié)合AFNetWorking沒準(zhǔn)會(huì)更好竞思。
一些啟發(fā)
-
分層的接口API設(shè)計(jì)。
#import "UIImageView+WebCache.h"
#import "UIButton+WebCache.h"
#import "UIImageView+HighlightedWebCache.h"
//以及其匯總的
#import "UIView+WebCache.h"
所有外層API與具體業(yè)務(wù)無關(guān)钞护。
使得SDWebImageManager
可以脫離View層單獨(dú)運(yùn)作盖喷。
-
線程安全
@synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];
}
if (url) {
@synchronized (self.failedURLs) {
isFailedUrl = [self.failedURLs containsObject:url];
}
}
.....
所有可能引起資源搶奪的對(duì)象操作、全部有條件鎖保護(hù)难咕。
但是由于內(nèi)嵌異常處理代碼的存在课梳、條件鎖的性能是所有鎖中最差的。不知道為什么SD中使用這么多余佃。
-
內(nèi)聯(lián)函數(shù)
更高效的短函數(shù)執(zhí)行惦界、替代表達(dá)式形式的宏定義。
-
精細(xì)的緩存管理原則
詳參上文提到的《磁盤清理的原則咙冗?》
-
回調(diào)設(shè)計(jì)
SDWebImage中使用了兩種沾歪、Block以及Delegate。
- Block使用的很多雾消、舉兩個(gè)例子灾搏。
======>#import "UIView+WebCache.h"
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock
context:(nullable NSDictionary *)context;
======>SDWebImageDownloader
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
再來看代理
@protocol SDWebImageManagerDelegate <NSObject>
@optional
/**
* Controls which image should be downloaded when the image is not found in the cache.
*
* @param imageManager The current `SDWebImageManager`
* @param imageURL The url of the image to be downloaded
*
* @return Return NO to prevent the downloading of the image on cache misses. If not implemented, YES is implied.
*/
- (BOOL)imageManager:(nonnull SDWebImageManager *)imageManager shouldDownloadImageForURL:(nullable NSURL *)imageURL;
- (nullable UIImage *)imageManager:(nonnull SDWebImageManager *)imageManager transformDownloadedImage:(nullable UIImage *)image withURL:(nullable NSURL *)imageURL;
不難看出、SDWebImage對(duì)回調(diào)的使用傾向于:
- Block
單個(gè)圖片的分類立润、單個(gè)圖片的下載狂窑。
每個(gè)操作任務(wù)中必現(xiàn)的progress以及completed。
所以桑腮、有很強(qiáng)的個(gè)體綁定需要或者使用次數(shù)不多時(shí)泉哈、傾向使用block - Delegate
SDWebImageManager下載完成之后的自定義圖片處理、是否下載某個(gè)url破讨。
這兩個(gè)方法如果需要的話都是將會(huì)調(diào)用多次的丛晦。所以、用Delegate更好提陶、可以將方法常駐烫沙。 - 同理
UITableView的使用Delegate、是用為在滾動(dòng)途中隙笆、代理方法需要被不斷的執(zhí)行锌蓄。
UIButton也是將會(huì)被多次點(diǎn)擊。
UIView的動(dòng)畫/GCD則可以使用Block撑柔、因?yàn)橹粓?zhí)行一次瘸爽、用完釋放。
所以铅忿、在日常使用中剪决、我們也可以參考上述原則進(jìn)行設(shè)計(jì)。 -
NSMapTable
用NSMapTable代替字典來存儲(chǔ)當(dāng)前正在進(jìn)行的操作、并且將value設(shè)置為NSMapTableWeakMemory昼捍。防止對(duì)應(yīng)value因?yàn)閺?qiáng)引用不能自動(dòng)釋放。
暫時(shí)想到的就這些肢扯、更多問題歡迎留言妒茬。
最后
本文主要是自己的學(xué)習(xí)與總結(jié)。如果文內(nèi)存在紕漏蔚晨、萬望留言斧正乍钻。如果不吝賜教小弟更加感謝。