SDWebImage是一個開源的第三方庫,它提供了UIImageView的分類來實現從網絡端下載數據並緩存到內存和磁盤。git
SDWebImage有以下特色:github
SDWebImage中每個下載任務都是一個SDWebImageDownloaderOperation
,而SDWebImageDownloaderOperation
又是繼承自NSOperation
,因此每個下載任務對應一個NSOperation
。在SDWebImage
中使用SDWebImageDownloader
來管理 多個下載任務,在SDWebImageDownloader
中有一個downloadedQueue
這個屬性,這個屬性是NSOperationQueue
類型的,也就是用NSOperationQueue
來管理NSOperation
。 下面咱們就一塊兒學習一下NSOperation
和NSOperationQueue
。web
NSOperation是一個抽象類,用來表示與單個任務相關聯的代碼和數據。緩存
NSOperation類是一個抽象類,咱們不能直接去使用它而是應該建立一個子類來繼承它。雖然它只是一個抽象類,可是它的基本實現仍是提供了頗有價值的邏輯來確保任務的安全執行。這種原生的邏輯可讓咱們專一於任務的真正的實現,而不須要用一些膠水代碼去確保這個任務能正常工做在其餘地方。安全
咱們能夠把一個NSOperation對象加入到一個operation queue中,讓這個operation queue去決定何時執行這個operation。**當使用operation queue去管理operation時,輪到某個operation執行時實際是去執行這個operation的start
方法,因此咱們一個operation對象實際要執行的任務應該放在start方法裏面。**若是咱們不想使用operation queue,也能夠經過手動調用NSOperation的start方法來執行任務。bash
咱們能夠經過添加依賴來肯定operation queue中operation的具體執行順序。添加依賴和移除依賴可使用下列的API:網絡
//添加依賴
- (void)addDependency:(NSOperation *)op;
//移除依賴
- (void)removeDependency:(NSOperation *)op;
複製代碼
只要當一個operation對象的全部依賴都執行完成的時候,其才能夠變成熟ready狀態,而後才能夠被執行。若是一個operation沒有添加依賴,直接加入了operation queue中,那麼就會按照加入隊列的前後順序,當這個operation的前一個operation執行完成之後,其狀態纔會變成ready,才能被執行。session
NSOperation對象有下列四個比較重要的狀態:app
isExecuting
, isFinished
, isReady
這三種狀態至關因而operation對象的生命週期:
而isCancelled
這種狀態則比較特殊,當咱們對operation對象調用- (void)cancel
方法時,其isCancelled
屬性會被置爲YES。這個時候要分兩種狀況:isExecuting
,調用- (void)cancel
方法後會立刻中止執行當前任務,而且狀態變爲isFinished
,isCancelled = Yes
。isReady
這個狀態以前,operation還沒開始執行,調用- (void)cancel
方法後會去調用operation的start
方法,在start
方法裏咱們要去處理cancel事件,並設置isFinished = YES
。
在SDWebImage中大量使用了option類型,經過判斷option類型的值來決定下一步應該怎麼作,因此若是對這些option值一點都不瞭解的話可能理解起源碼來也會很是難受。SDWebImageOptions
是暴露在外的可供使用者使用的option。還有一些option好比SDImageCacheOptions
, SDWebImageDownloaderOptions
這些都是不暴露給用戶使用的。源碼中是根據用戶設置的SDWebImageOptions
這個option來肯定另外兩個option的值。 下面咱們來具體看一下SDWebImageOptions
框架
SDImageCacheOptions
中的三個選項在SDWebImageOptions
中都有對應的選項。
SDImageCacheQueryDataWhenInMemory
對應SDWebImageQueryDataWhenInMemory
。SDImageCacheQueryDiskSync
對應SDWebImageQueryDiskSync
。SDImageCacheScaleDownLargeImages
對應SDWebImageScaleDownLargeImages
。SDWebImageDownloaderOptions
SDWebImageDownloaderOptions
中全部選項在SDWebImageOptions
中也有相對應的選項,這裏再也不贅述。
SDImageCacheType
//從緩存中得不到數據
SDImageCacheTypeNone,
//從磁盤緩存中獲得數據
SDImageCacheTypeDisk,
//從內存緩存中獲得數據
SDImageCacheTypeMemory
複製代碼
UIImageView (WebCache)
,而後UIImageView對象能夠調用這個分類的方法來下載圖片:[imageView sd_setImageWithURL:[NSURL URLWithString:@""]];
複製代碼
UIImageView (WebCache)
的- (void)sd_setImageWithURL:(nullable NSURL *)url
方法實際調用了UIView (WebCache)
的下列方法:- (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;
複製代碼
UIView (WebCache)
的上述方法在實現時會建立一個SDWebImageManager
的實例對象,而後調用其下列方法來加載圖片:- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock;
複製代碼
SDWebImageManager
對象的上述方法裏,首先會查詢在緩存中有沒有這個圖片,而後根據各類option的判斷決定是否要從網絡端下載。查詢緩存中有沒有是經過調用SDImageCache
對象的實例方法來實現的:- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key
options:(SDImageCacheOptions)options
done:(nullable SDCacheQueryCompletedBlock)doneBlock;
複製代碼
SDWebImageDownloader
對象的下列方法進行下載:- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
複製代碼
SDWebImageManager
對象獲取的圖片顯示在UIImageView
上。這一部分咱們進行詳細的源碼分析。 首先從SDWebImageManager
類的loadImageWithURL:
方法看起: 因爲代碼比較長,我就採用註釋的方式
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
// Invoking this method without a completedBlock is pointless
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
//若是傳進來的是一個NSString,則把NSString轉化爲NSURL
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
// Prevents app crashing on argument type error like sending NSNull instead of NSURL
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}
SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
operation.manager = self;
//self.failedURLs是nsurl的黑名單,通常狀況下,若是URL在這個黑名單裏,那麼就不會嘗試加載這個圖片,直接返回
BOOL isFailedUrl = NO;
if (url) {
LOCK(self.failedURLsLock);
isFailedUrl = [self.failedURLs containsObject:url];
UNLOCK(self.failedURLsLock);
}
//SDWebImageRetryFailed即即便URL被加入了黑名單,也要嘗試加載這個URL對應的圖片
//若是URL長度爲0,或者URL被加入了黑名單而且沒有設置SDWebImageRetryFailed,那麼就直接回調完成的block
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url];
return operation;
}
LOCK(self.runningOperationsLock);
[self.runningOperations addObject:operation];
UNLOCK(self.runningOperationsLock);
NSString *key = [self cacheKeyForURL:url];
//因爲咱們在使用API的時候只設置SDWebImageOptions,因此這裏就是根據用戶設置的SDWebImageOptions去設置SDImageCacheOptions
SDImageCacheOptions cacheOptions = 0;
if (options & SDWebImageQueryDataWhenInMemory) cacheOptions |= SDImageCacheQueryDataWhenInMemory;
if (options & SDWebImageQueryDiskSync) cacheOptions |= SDImageCacheQueryDiskSync;
if (options & SDWebImageScaleDownLargeImages) cacheOptions |= SDImageCacheScaleDownLargeImages;
__weak SDWebImageCombinedOperation *weakOperation = operation;
//這裏開始調用SDImageCache對象的queryCacheOperationForKey:方法去緩存中查找有沒有這個URL對應的圖片
operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key options:cacheOptions done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (!strongOperation || strongOperation.isCancelled) {
[self safelyRemoveOperationFromRunning:strongOperation];
return;
}
// 判斷咱們是否須要從網絡端下載圖片
//首先檢查沒有設置只能從緩存中獲取,而後檢查cachedImage = nil或者設置了要刷新緩存,則須要從網絡端下載圖片
BOOL shouldDownload = (!(options & SDWebImageFromCacheOnly))
&& (!cachedImage || options & SDWebImageRefreshCached)
&& (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]);
if (shouldDownload) {
//從緩存中獲取了圖片而且設置了要刷新緩存這個option,則要進行兩次完成的回調,這是第一次回調
if (cachedImage && options & SDWebImageRefreshCached) {
// If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image
// AND try to re-download it in order to let a chance to NSURLCache to refresh it from server.
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
}
// download if no image or requested to refresh anyway, and download allowed by delegate
//這裏是根據用戶設置的SDWebImageOptions來手動設置SDWebImageDownloaderOptions
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) {
//這裏其實就是把SDWebImageDownloaderProgressiveDownload這個option去掉
downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
// ignore image read from NSURLCache if image if cached but force refreshing
//加上SDWebImageDownloaderIgnoreCachedResponse這個option,忽略NSURLCache中緩存的response
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}
__weak typeof(strongOperation) weakSubOperation = strongOperation;
//l開始進行圖片的下載
strongOperation.downloadToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
__strong typeof(weakSubOperation) strongSubOperation = weakSubOperation;
if (!strongSubOperation || strongSubOperation.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:strongSubOperation completion:completedBlock error:error url:url];
BOOL shouldBlockFailedURL;
// 後面都是判斷在請求失敗的狀況下是否應該把
if ([self.delegate respondsToSelector:@selector(imageManager:shouldBlockFailedURL:withError:)]) {
shouldBlockFailedURL = [self.delegate imageManager:self shouldBlockFailedURL:url withError:error];
} else {
shouldBlockFailedURL = ( error.code != NSURLErrorNotConnectedToInternet
&& error.code != NSURLErrorCancelled
&& error.code != NSURLErrorTimedOut
&& error.code != NSURLErrorInternationalRoamingOff
&& error.code != NSURLErrorDataNotAllowed
&& error.code != NSURLErrorCannotFindHost
&& error.code != NSURLErrorCannotConnectToHost
&& error.code != NSURLErrorNetworkConnectionLost);
}
if (shouldBlockFailedURL) {
LOCK(self.failedURLsLock);
[self.failedURLs addObject:url];
UNLOCK(self.failedURLsLock);
}
}
else {
//若是設置了SDWebImageRetryFailed那麼就要把URL從黑名單中移除
if ((options & SDWebImageRetryFailed)) {
LOCK(self.failedURLsLock);
[self.failedURLs removeObject:url];
UNLOCK(self.failedURLsLock);
}
//判斷是否應該把下載的圖片緩存到磁盤,SDWebImageCacheMemoryOnly這個option表示只把圖片緩存到內存
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
// We've done the scale process in SDWebImageDownloader with the shared manager, this is used for custom manager and avoid extra scale. if (self != [SDWebImageManager sharedManager] && self.cacheKeyFilter && downloadedImage) { downloadedImage = [self scaledImageForKey:key image:downloadedImage]; } if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) { // Image refresh hit the NSURLCache cache, do not call the completion block } 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), ^{ @autoreleasepool { UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url]; if (transformedImage && finished) { BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage]; NSData *cacheData; // pass nil if the image was transformed, so we can recalculate the data from the image if (self.cacheSerializer) { cacheData = self.cacheSerializer(transformedImage, (imageWasTransformed ? nil : downloadedData), url); } else { cacheData = (imageWasTransformed ? nil : downloadedData); } [self.imageCache storeImage:transformedImage imageData:cacheData forKey:key toDisk:cacheOnDisk completion:nil]; } [self callCompletionBlockForOperation:strongSubOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url]; } }); } else { //能夠直接看到這一部分 if (downloadedImage && finished) { if (self.cacheSerializer) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ @autoreleasepool { NSData *cacheData = self.cacheSerializer(downloadedImage, downloadedData, url); [self.imageCache storeImage:downloadedImage imageData:cacheData forKey:key toDisk:cacheOnDisk completion:nil]; } }); } else { //對圖片進行緩存 [self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil]; } } //第二次調用完成的block [self callCompletionBlockForOperation:strongSubOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url]; } } if (finished) { [self safelyRemoveOperationFromRunning:strongSubOperation]; } }]; //若是從從緩存中獲取了圖片而且不須要下載 } else if (cachedImage) { //執行完成的回調 [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url]; [self safelyRemoveOperationFromRunning:strongOperation]; } else { // 緩存中沒有獲取圖片,也不用下載 [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url]; [self safelyRemoveOperationFromRunning:strongOperation]; } }]; return operation; } 複製代碼
總結一下SDWebImageManager
的loadImageWithURL:
所作的事情:
其實在
loadImageWithURL:
裏面作了加載圖片的完整流程。首先檢查傳入的NSURL的有效性。而後開始從緩存中查找是否有這個圖片,獲得查詢結果以後再根據查詢結果和設置的option判斷是否須要進行下載操做,若是不須要下載操做那麼就直接使用cache image進行下載回調。若是須要進行下載操做那麼就開始下載,下載完成後按照設置的option將圖片緩存到內存和磁盤,最後進行完成的回調。
而後咱們看一下查詢緩存的具體過程,也就是SDImageCache
這個類的queryCacheOperationForKey:
方法: 這裏也是採用註釋的方式
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDCacheQueryCompletedBlock)doneBlock {
if (!key) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
// 首先檢查內存緩存中有沒有這個圖片,注意內存緩存使用的是NSCache,它是一個類字典結構,使用圖片對應的nsurl做爲key,在查詢的時候就用這個key去查詢
UIImage *image = [self imageFromMemoryCacheForKey:key];
//是否只查詢內存緩存(若是從內存緩存中獲取了圖片而且沒有設置SDImageCacheQueryDataWhenInMemory,那麼就只查詢內存緩存)
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;
//j緩存獲取的類型,有三種m類型,none,memory,disk
SDImageCacheType cacheType = SDImageCacheTypeNone;
if (image) {
// 圖片是從內存緩存中獲取的
diskImage = image;
cacheType = SDImageCacheTypeMemory;
} else if (diskData) {
//圖片是從磁盤緩存中獲取的
cacheType = SDImageCacheTypeDisk;
// 解壓圖片
diskImage = [self diskImageForKey:key data:diskData options:options];
//判斷是否須要把圖片緩存到內存
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
//將圖片緩存到內存
[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;
}
複製代碼
總結一下queryCacheOperationForKey:
方法所作的事情:
SDImageCache
這個類是專門負責緩存相關的問題的,包括查詢緩存和將圖片進行緩存。SDImageCache
使用了一個NSCache
對象來進行內存緩存,磁盤緩存則是把圖片數據存放在應用沙盒的Caches
這個文件夾下。
首先查詢內存緩存,內存緩存查詢完了之後再判斷是否須要查詢磁盤緩存。若是查詢內存緩存已經有告終果而且沒有設置必定要查詢磁盤緩存,那麼就不查詢磁盤緩存,不然就要查詢磁盤緩存。內存緩存沒有查詢到圖片,而且磁盤緩存查詢到了圖片,那麼就要把這個內容緩存到內存緩存中。
圖片的緩存查詢完成後咱們再來看一下下載操做,即SDWebImageDownloader
的downloadImageWithURL:
方法
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
// 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;
}
LOCK(self.operationsLock);
NSOperation<SDWebImageDownloaderOperationInterface> *operation = [self.URLOperations objectForKey:url];
if (!operation || operation.isFinished) {
//建立一下下載的operation
operation = [self createDownloaderOperationWithUrl:url options:options];
__weak typeof(self) wself = self;
operation.completionBlock = ^{
__strong typeof(wself) sself = wself;
if (!sself) {
return;
}
LOCK(sself.operationsLock);
[sself.URLOperations removeObjectForKey:url];
UNLOCK(sself.operationsLock);
};
[self.URLOperations setObject:operation forKey:url];
// Add operation to operation queue only after all configuration done according to Apple's doc. // `addOperation:` does not synchronously execute the `operation.completionBlock` so this will not cause deadlock. //把operation加入到nNSOperationQueue中去 [self.downloadQueue addOperation:operation]; } UNLOCK(self.operationsLock); //這一部分代碼是在取消operation的時候使用 id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock]; SDWebImageDownloadToken *token = [SDWebImageDownloadToken new]; token.downloadOperation = operation; token.url = url; token.downloadOperationCancelToken = downloadOperationCancelToken; return token; } 複製代碼
SDWebImageDownloader
這個類是專門管理下載的,它有一個屬性是downloadQueue
,這是一個NSOperationQueue
,每建立一個新的下載任務都把它加入到這個downloadQueue
中,讓downloadQueue
去管理任務的開始,取消,結束。
上面的方法其實作的事情很簡單,就是建立了一個下載圖片的operation,而後把它加入到了
downloadQueue
中去。
下面咱們來具體看一下建立下載圖片的operation的過程,即SDWebImageDownloader
類的createDownloaderOperationWithUrl:
方法:
- (NSOperation<SDWebImageDownloaderOperationInterface> *)createDownloaderOperationWithUrl:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options {
NSTimeInterval timeoutInterval = self.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
NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
cachePolicy:cachePolicy
timeoutInterval:timeoutInterval];
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
request.HTTPShouldUsePipelining = YES;
if (self.headersFilter) {
request.allHTTPHeaderFields = self.headersFilter(url, [self allHTTPHeaderFields]);
}
else {
request.allHTTPHeaderFields = [self allHTTPHeaderFields];
}
//前面都是爲了建立一個request,而後使用request和session對象去建立下載的operation
NSOperation<SDWebImageDownloaderOperationInterface> *operation = [[self.operationClass alloc] initWithRequest:request inSession:self.session options:options];
operation.shouldDecompressImages = self.shouldDecompressImages;
if (self.urlCredential) {
operation.credential = self.urlCredential;
} else if (self.username && self.password) {
operation.credential = [NSURLCredential credentialWithUser:self.username password:self.password persistence:NSURLCredentialPersistenceForSession];
}
//設置operation的隊列優先級
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
}
//若是設置的執行順序是xLIFI,即後進先出,則要把queue中的最後一個加入的operation的依賴設置爲該operation,這樣來保證這個operation最早執行
if (self.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
// Emulate LIFO execution order by systematically adding new operations as last operation's dependency [self.lastAddedOperation addDependency:operation]; self.lastAddedOperation = operation; } return operation; } 複製代碼
這個方法就是建立了一個request對象,而後使用這個request對象和session對象去建立下載的operation對象。
咱們看一下負責單個下載任務的operation對象究竟是怎麼建立的,即SDWebImageDownloaderOperation
類的- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request inSession:(nullable NSURLSession *)session options:(SDWebImageDownloaderOptions)options
方法:
- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
inSession:(nullable NSURLSession *)session
options:(SDWebImageDownloaderOptions)options {
if ((self = [super init])) {
_request = [request copy];
_shouldDecompressImages = YES;
_options = options;
_callbackBlocks = [NSMutableArray new];
_executing = NO;
_finished = NO;
_expectedSize = 0;
_unownedSession = session;
_callbacksLock = dispatch_semaphore_create(1);
_coderQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationCoderQueue", DISPATCH_QUEUE_SERIAL);
}
return self;
}
複製代碼
這個初始化方法其實也很簡單,就是給本身的成員變量賦值
咱們知道,NSOperation類的真正執行任務是在其start方法裏面,那麼咱們看一下SDWebImageDownloaderOperation
的start
方法的具體實現: 代碼比較長,我在關鍵部分加了註釋
- (void)start {
@synchronized (self) {
if (self.isCancelled) {
self.finished = YES;
[self reset];
return;
}
#if SD_UIKIT
//這一部分就是解決在後臺仍然進行下載的問題
Class UIApplicationClass = NSClassFromString(@"UIApplication");
BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
__weak __typeof__ (self) wself = self;
UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
__strong __typeof (wself) sself = wself;
if (sself) {
[sself cancel];
[app endBackgroundTask:sself.backgroundTaskId];
sself.backgroundTaskId = UIBackgroundTaskInvalid;
}
}];
}
#endif
NSURLSession *session = self.unownedSession;
//建立一個session對象,由於後面要建立NSURLSessionTask,須要session對象
if (!session) {
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.timeoutIntervalForRequest = 15;
/**
* 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.
*/
session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];
self.ownedSession = session;
}
if (self.options & SDWebImageDownloaderIgnoreCachedResponse) {
// Grab the cached data for later check
NSURLCache *URLCache = session.configuration.URLCache;
if (!URLCache) {
URLCache = [NSURLCache sharedURLCache];
}
NSCachedURLResponse *cachedResponse;
// NSURLCache's `cachedResponseForRequest:` is not thread-safe, see https://developer.apple.com/documentation/foundation/nsurlcache#2317483 @synchronized (URLCache) { cachedResponse = [URLCache cachedResponseForRequest:self.request]; } if (cachedResponse) { self.cachedData = cachedResponse.data; } } //建立dataTask,這個纔是真正執行下載任務的 self.dataTask = [session dataTaskWithRequest:self.request]; self.executing = YES; } if (self.dataTask) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunguarded-availability" if ([self.dataTask respondsToSelector:@selector(setPriority:)]) { if (self.options & SDWebImageDownloaderHighPriority) { self.dataTask.priority = NSURLSessionTaskPriorityHigh; } else if (self.options & SDWebImageDownloaderLowPriority) { self.dataTask.priority = NSURLSessionTaskPriorityLow; } } #pragma clang diagnostic pop [self.dataTask resume]; for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) { progressBlock(0, NSURLResponseUnknownLength, self.request.URL); } __weak typeof(self) weakSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:weakSelf]; }); } else { [self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorUnknown userInfo:@{NSLocalizedDescriptionKey : @"Task can't be initialized"}]]; [self done]; return; } #if SD_UIKIT Class UIApplicationClass = NSClassFromString(@"UIApplication"); if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) { return; } if (self.backgroundTaskId != UIBackgroundTaskInvalid) { UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)]; [app endBackgroundTask:self.backgroundTaskId]; self.backgroundTaskId = UIBackgroundTaskInvalid; } #endif } 複製代碼
這裏就是經過一個session對象和一個request對象建立了一個dataTask對象,這個dataTask對象纔是真正用來下載的,而後調用
[self.dataTask resume]
執行下載。
到這裏SDWebImage的源碼分析就結束啦。
參考: SDWebImage實現分析
這篇文章在簡書的地址:SDWebImage源碼解讀