SDWebImage源碼解讀之SDWebImageDownloader
第八篇
前言
SDWebImageDownloader這個類非常簡單,作者的設計思路也很清晰,但是我想在這說點題外話。
如果有人問你:你怎么看待編程這件事?你怎么回答。這個問題是我在看這個類的時候,忽然出現在我腦子中的。我突然意識到,其實不管是函數還是屬性,他們都是數據。我們編寫的所有程序都是在處理數據。函數本身也是一種特殊的數據。
真正難的是生產數據的這一過程。舉個例子,給你一堆菜籽,要求生產出油來。怎么辦?我們首先為這個任務設計一個函數:
- (油)用菜籽生產油(菜籽);
這就是我們最外層的函數,也應該是我們最開始想到的函數。然后經過我們的研究發現,這個生產過程很復雜,必須分工合作才能實現。於是我們把這個任務分割為好幾個小任務:
1. - (干凈的菜籽)取出雜質(菜籽);
2. - (炒熟的菜籽)把菜籽炒一下(干凈的菜籽);
3. - (蒸了的菜籽)把菜籽蒸一下(炒熟的菜籽);
4. - (捆好的菜籽)把菜籽包捆成一塊(蒸了的菜籽);
5. - (油)撞擊菜籽包(捆好的菜籽);
大家有沒有發現,整個榨油的過程就是對數據的處理。這一點其實很重要。如果沒有把- (油)用菜籽生產油(菜籽);這一任務進行拆分,我們就會寫出復雜無比的函數。那么就有人要問了,只要實現這個功能就行了唄。其實這往往是寫不出好代碼的原因。
整個任務的設計應該是事先就設計好的。任務被分割成更小更簡單的部分,然后再去實現這些最小的任務,不應該是編寫邊分割任務,往往臨時分割的任務(也算是私有函數吧)沒有最正確的界限。
有了上邊合理的分工之后呢,我們就可以進行任務安排了。我們回到現實開發中來。上邊5個子任務的難度是不同的。有的人可能基礎比較差,那么讓他去干篩菜籽這種體力活,應該沒問題。那些炒或者蒸的子任務是要掌握火候的,也就是說有點技術含量。那么就交給能勝任這項工作的人去做。所有的這一切,我們只要事先定義好各自的生產結果就行了,完全不影響每個程序的執行。
怎么樣?大家體會到這種編程設計的好處了嗎?我還可以進行合並,把炒和煮合成一個小組,完全可行啊。好了這方面的思考就說這么多吧。如果我想買煮熟了的菜籽,是不是也很簡單?
有的人用原始的撞擊菜籽包榨油,有的人卻用最先進的儀器榨油,這就是編程技術和知識深度的區別啊。
SDWebImageDownloaderOptions
言歸正傳,當我們需要給某個功能添加Options的時候,一般使用枚舉來實現。我們先看看支持的選項:
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
SDWebImageDownloaderLowPriority = 1 << 0,
SDWebImageDownloaderProgressiveDownload = 1 << 1, // 帶有進度
SDWebImageDownloaderUseNSURLCache = 1 << 2, // 使用URLCache
SDWebImageDownloaderIgnoreCachedResponse = 1 << 3, // 不緩存響應
SDWebImageDownloaderContinueInBackground = 1 << 4, // 支持后台下載
SDWebImageDownloaderHandleCookies = 1 << 5, // 使用Cookies
SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6, // 允許驗證SSL
SDWebImageDownloaderHighPriority = 1 << 7, // 高權限
SDWebImageDownloaderScaleDownLargeImages = 1 << 8, // 裁剪大圖片
};
這里提供了這么幾種不同的選項,大家可以根據自己的需求選個合適的選項。這里作者使用了掩碼。比如說,1 << 1 ,表示把1左移一位,我們把1攜程二進制為:00000001,那么左移一位后就是:00000010 轉成10進制后就是2,也就是說左移一位表示在原來的值上乘以2。
再舉個例子,當判斷self.option是否是SDWebImageDownloaderIgnoreCachedResponse選項時,應該這么判斷:
self.option & SDWebImageDownloaderIgnoreCachedResponse
SDWebImageDownloaderExecutionOrder
SDWebImageDownloaderExecutionOrder定義了數據被調用的順序。按照一般的想法。下載應該按照數據放入隊列的順序依次進行,但也支持后進先出這種方式。
一個下載管理器應該這樣管理下載,肯定有一個下載列表,我們可以假定這個列表保存在一個數組中,正常情況下我們應該每次取出數組中第1個元素來下載,這就是FIFO(先進先出)。那么我們要改為LIFO(后進先出),應該是針對某一個下載的,不應該是把取出數據的順序改為從數組的最后一個元素取出。
typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
/**
* Default value. All download operations will execute in queue style (first-in-first-out).
*/
SDWebImageDownloaderFIFOExecutionOrder,
/**
* All download operations will execute in stack style (last-in-first-out).
*/
SDWebImageDownloaderLIFOExecutionOrder
};
但是我有一個疑問,如果我只是想暫停某一個下載,我該怎么辦呢?如果直接取消,那么點擊繼續的時候,要重新開啟下載,肯定影響體驗。這一點跟使用數組管理任務有點不同。
輔助部分
extern NSString * _Nonnull const SDWebImageDownloadStartNotification;
extern NSString * _Nonnull const SDWebImageDownloadStopNotification;
通過extern,我們就能夠使用其他文件的代碼。
typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL);
typedef void(^SDWebImageDownloaderCompletedBlock)(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished);
命名Block
typedef NSDictionary<NSString *, NSString *> SDHTTPHeadersDictionary;
typedef NSMutableDictionary<NSString *, NSString *> SDHTTPHeadersMutableDictionary;
命名字典
typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterBlock)(NSURL * _Nullable url, SDHTTPHeadersDictionary * _Nullable headers);
這個block允許我們自定義請求頭,通過Block傳值,有一定的優點,我們可以拿到一些參數,然后在加工成我們需要的數據,最后返回。
SDWebImageDownloadToken
SDWebImageDownloadToken作為每一個下載的唯一身份標識,SDWebImageDownloader和我們平時開發中的下載還是又不一樣的地方的,它弱化了下載過程,比較強調的是下載結果。不支持斷點下載(當然這可能沒有必要)。
如果我們需要設計一個自己的下載管理者,我們就應該設計一個類似SDWebImageDownloadToken這樣的下載對象封裝類,我需要的所有信息,都能在這個對象中獲取,大家如果有興趣,可以看看MCDownloadManager.
/**
* A token associated with each download. Can be used to cancel a download
*/
@interface SDWebImageDownloadToken : NSObject
@property (nonatomic, strong, nullable) NSURL *url;
@property (nonatomic, strong, nullable) id downloadOperationCancelToken;
@end
SDWebImageDownloader.h
/**
* Decompressing images that are downloaded and cached can improve performance but can consume lot of memory.
* Defaults to YES. Set this to NO if you are experiencing a crash due to excessive memory consumption.
*/
@property (assign, nonatomic) BOOL shouldDecompressImages;
/**
* The maximum number of concurrent downloads
*/
@property (assign, nonatomic) NSInteger maxConcurrentDownloads;
/**
* Shows the current amount of downloads that still need to be downloaded
*/
@property (readonly, nonatomic) NSUInteger currentDownloadCount;
/**
* The timeout value (in seconds) for the download operation. Default: 15.0.
*/
@property (assign, nonatomic) NSTimeInterval downloadTimeout;
/**
* Changes download operations execution order. Default value is `SDWebImageDownloaderFIFOExecutionOrder`.
*/
@property (assign, nonatomic) SDWebImageDownloaderExecutionOrder executionOrder;
/**
* Singleton method, returns the shared instance
*
* @return global shared instance of downloader class
*/
+ (nonnull instancetype)sharedDownloader;
/**
* Set the default URL credential to be set for request operations.
*/
@property (strong, nonatomic, nullable) NSURLCredential *urlCredential;
/**
* Set username
*/
@property (strong, nonatomic, nullable) NSString *username;
/**
* Set password
*/
@property (strong, nonatomic, nullable) NSString *password;
/**
* Set filter to pick headers for downloading image HTTP request.
*
* This block will be invoked for each downloading image request, returned
* NSDictionary will be used as headers in corresponding HTTP request.
*/
@property (nonatomic, copy, nullable) SDWebImageDownloaderHeadersFilterBlock headersFilter;
初始化方法
/**
* Creates an instance of a downloader with specified session configuration.
* *Note*: `timeoutIntervalForRequest` is going to be overwritten.
* @return new instance of downloader class
*/
- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration NS_DESIGNATED_INITIALIZER;
我們都知道: 一個NSURLSession會話,使用NSURLSessionConfiguration進行配置,利用這一點,我們在設計自己的網絡框架的時候,可以參考NSURLSessionConfiguration。暴露少量的屬性來配置網絡請求,后期維護起來也比較容易。
使用NS_DESIGNATED_INITIALIZER強調該方法是建議的初始化方法。
其他的方法
/**
* Set a value for a HTTP header to be appended to each download HTTP request.
*
* @param value The value for the header field. Use `nil` value to remove the header.
* @param field The name of the header field to set.
*/
- (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field;
/**
* Returns the value of the specified HTTP header field.
*
* @return The value associated with the header field field, or `nil` if there is no corresponding header field.
*/
- (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field;
/**
* Sets a subclass of `SDWebImageDownloaderOperation` as the default
* `NSOperation` to be used each time SDWebImage constructs a request
* operation to download an image.
*
* @param operationClass The subclass of `SDWebImageDownloaderOperation` to set
* as default. Passing `nil` will revert to `SDWebImageDownloaderOperation`.
*/
- (void)setOperationClass:(nullable Class)operationClass;
/**
* Creates a SDWebImageDownloader async downloader instance with a given URL
*
* The delegate will be informed when the image is finish downloaded or an error has happen.
*
* @see SDWebImageDownloaderDelegate
*
* @param url The URL to the image to download
* @param options The options to be used for this download
* @param progressBlock A block called repeatedly while the image is downloading
* @note the progress block is executed on a background queue
* @param completedBlock A block called once the download is completed.
* If the download succeeded, the image parameter is set, in case of error,
* error parameter is set with the error. The last parameter is always YES
* if SDWebImageDownloaderProgressiveDownload isn't use. With the
* SDWebImageDownloaderProgressiveDownload option, this block is called
* repeatedly with the partial image object and the finished argument set to NO
* before to be called a last time with the full image and finished argument
* set to YES. In case of error, the finished argument is always YES.
*
* @return A token (SDWebImageDownloadToken) that can be passed to -cancel: to cancel this operation
*/
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
/**
* Cancels a download that was previously queued using -downloadImageWithURL:options:progress:completed:
*
* @param token The token received from -downloadImageWithURL:options:progress:completed: that should be canceled.
*/
- (void)cancel:(nullable SDWebImageDownloadToken *)token;
/**
* Sets the download queue suspension state
*/
- (void)setSuspended:(BOOL)suspended;
/**
* Cancels all download operations in the queue
*/
- (void)cancelAllDownloads;
SDWebImageDownloader.m
@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;
@property (weak, nonatomic, nullable) NSOperation *lastAddedOperation;
@property (assign, nonatomic, nullable) Class operationClass;
@property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, SDWebImageDownloaderOperation *> *URLOperations;
@property (strong, nonatomic, nullable) SDHTTPHeadersMutableDictionary *HTTPHeaders;
// This queue is used to serialize the handling of the network responses of all the download operation in a single queue
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t barrierQueue;
// The session in which data tasks will run
@property (strong, nonatomic) NSURLSession *session;
這些屬性可以說都是為了完成管理下載任務而存在的。
downloadQueue隊列lastAddedOperation用於記錄最后添加的操作operationClass支持我們自定義的操作類URLOperations存放着所有的operationHTTPHeadersHTTP請求頭barrierQueuesession
initialize
initialize 和 load 這兩個方法比較特殊,我們通過下邊這個表格來看看他們的區別
| .. | +(void)load | +(void)initialize |
|---|---|---|
| 執行時機 | 在程序運行后立即執行 | 在類的方法第一次被調時執行 |
| 若自身未定義,是否沿用父類的方法? | 否 | 是 |
| 類別中的定義 | 全都執行,但后於類中的方法 | 覆蓋類中的方法,只執行一個 |
+ (void)initialize {
// Bind SDNetworkActivityIndicator if available (download it here: http://github.com/rs/SDNetworkActivityIndicator )
// To use it, just add #import "SDNetworkActivityIndicator.h" in addition to the SDWebImage import
if (NSClassFromString(@"SDNetworkActivityIndicator")) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")];
#pragma clang diagnostic pop
// Remove observer in case it was previously added.
[[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStartNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStopNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:activityIndicator
selector:NSSelectorFromString(@"startActivity")
name:SDWebImageDownloadStartNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:activityIndicator
selector:NSSelectorFromString(@"stopActivity")
name:SDWebImageDownloadStopNotification object:nil];
}
}
上邊的方法是為了給圖片下載綁定一個SDNetworkActivityIndicator,只有當這個SDNetworkActivityIndicator文件存在的情況下才會執行,目的就是當下在圖片是,狀態欄會轉小菊花。
初始化實現
- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration {
if ((self = [super init])) {
_operationClass = [SDWebImageDownloaderOperation class];
_shouldDecompressImages = YES;
_executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
_downloadQueue = [NSOperationQueue new];
_downloadQueue.maxConcurrentOperationCount = 6;
_downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
_URLOperations = [NSMutableDictionary new];
#ifdef SD_WEBP
_HTTPHeaders = [@{@"Accept": @"image/webp,image/*;q=0.8"} mutableCopy];
#else
_HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy];
#endif
_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
_downloadTimeout = 15.0;
sessionConfiguration.timeoutIntervalForRequest = _downloadTimeout;
/**
* Create the session for this task
* We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
* method calls and completion handler calls.
*/
self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration
delegate:self
delegateQueue:nil];
}
return self;
}
這里邊做了必要的初始化,默認最大支持的並發數為6個,也就是說可以同時下載6張圖片。**我們看看image/webp,image/*;q=0.8是什么意思,image/webp是web格式的圖片,q=0.8指的是權重系數為0.8,q的取值范圍是0 - 1, 默認值為1,q作用於它前邊分號;前邊的內容。在這里,image/webp,image/*;q=0.8表示優先接受image/webp,其次接受image/*的圖片。
set或者get方法
- (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field {
if (value) {
self.HTTPHeaders[field] = value;
}
else {
[self.HTTPHeaders removeObjectForKey:field];
}
}
- (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field {
return self.HTTPHeaders[field];
}
- (void)setMaxConcurrentDownloads:(NSInteger)maxConcurrentDownloads {
_downloadQueue.maxConcurrentOperationCount = maxConcurrentDownloads;
}
- (NSUInteger)currentDownloadCount {
return _downloadQueue.operationCount;
}
- (NSInteger)maxConcurrentDownloads {
return _downloadQueue.maxConcurrentOperationCount;
}
- (void)setOperationClass:(nullable Class)operationClass {
if (operationClass && [operationClass isSubclassOfClass:[NSOperation class]] && [operationClass conformsToProtocol:@protocol(SDWebImageDownloaderOperationInterface)]) {
_operationClass = operationClass;
} else {
_operationClass = [SDWebImageDownloaderOperation class];
}
}
為每個URL綁定事件
- (nullable SDWebImageDownloadToken *)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock
completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
forURL:(nullable NSURL *)url
createCallback:(SDWebImageDownloaderOperation *(^)())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();
self.URLOperations[url] = operation;
__weak SDWebImageDownloaderOperation *woperation = operation;
operation.completionBlock = ^{
SDWebImageDownloaderOperation *soperation = woperation;
if (!soperation) return;
if (self.URLOperations[url] == soperation) {
[self.URLOperations removeObjectForKey:url];
};
};
}
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
token = [SDWebImageDownloadToken new];
token.url = url;
token.downloadOperationCancelToken = downloadOperationCancelToken;
});
return token;
}
每一個URL都會被作為每個下載的唯一標識,每一個下載都會綁定一個progressBlock和completeBlock,最終我們還要使用這個URL跟NSOperation建立聯系。
關於如何創建SDWebImageDownloaderOperation
這一段代碼,我采用注釋的方式,大家看注釋就行了。
- (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 *{
__strong __typeof (wself) sself = wself;
// 1.設置超時時間
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
// 2.創建request,注意我們設置的緩存策略的選擇
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
request.HTTPShouldUsePipelining = YES;
// 3.設置請求頭部
if (sself.headersFilter) {
request.allHTTPHeaderFields = sself.headersFilter(url, [sself.HTTPHeaders copy]);
}
else {
request.allHTTPHeaderFields = sself.HTTPHeaders;
}
// 4.創建操作對象
SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options];
operation.shouldDecompressImages = sself.shouldDecompressImages;
// 5.給操作對象設置urlCredential
if (sself.urlCredential) {
operation.credential = sself.urlCredential;
} else if (sself.username && sself.password) {
operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession];
}
// 6.設置操作級別
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
}
// 7.把操作添加到隊列
[sself.downloadQueue addOperation:operation];
// 8.根據executionOrder設置,設置依賴
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;
}];
}
取消摸個操作
- (void)cancel:(nullable SDWebImageDownloadToken *)token {
dispatch_barrier_async(self.barrierQueue, ^{
SDWebImageDownloaderOperation *operation = self.URLOperations[token.url];
BOOL canceled = [operation cancel:token.downloadOperationCancelToken];
if (canceled) {
[self.URLOperations removeObjectForKey:token.url];
}
});
}
全部暫停或取消
- (void)setSuspended:(BOOL)suspended {
(self.downloadQueue).suspended = suspended;
}
- (void)cancelAllDownloads {
[self.downloadQueue cancelAllOperations];
}
會話的代理
我們在初始化方法中創建了會話:
self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration
delegate:self
delegateQueue:nil];
delegate設置為自己,也就是當使用這個會話請求數據,收到響應時,會調用SDWebImageDownloader.m中的代理方法,然后再調用SDWebImageDownloaderOperation中的代理方法處理事情。
那么作者為什么這么設計呢?目的就是為了共用一個URLSession。
- (SDWebImageDownloaderOperation *)operationWithTask:(NSURLSessionTask *)task {
SDWebImageDownloaderOperation *returnOperation = nil;
for (SDWebImageDownloaderOperation *operation in self.downloadQueue.operations) {
if (operation.dataTask.taskIdentifier == task.taskIdentifier) {
returnOperation = operation;
break;
}
}
return returnOperation;
}
#pragma mark NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
[dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
[dataOperation URLSession:session dataTask:dataTask didReceiveData:data];
}
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
[dataOperation URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler];
}
#pragma mark NSURLSessionTaskDelegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task];
[dataOperation URLSession:session task:task didCompleteWithError:error];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler {
completionHandler(request);
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task];
[dataOperation URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler];
}
由於個人知識有限,如有錯誤之處,還望各路大俠給予指出啊
- SDWebImage源碼解讀 之 NSData+ImageContentType 簡書 博客園
- SDWebImage源碼解讀 之 UIImage+GIF 簡書 博客園
- SDWebImage源碼解讀 之 SDWebImageCompat 簡書 博客園
- SDWebImage源碼解讀 之SDWebImageDecoder 簡書 博客園
- SDWebImage源碼解讀 之SDWebImageCache(上) 簡書 博客園
- SDWebImage源碼解讀之SDWebImageCache(下) 簡書 博客園
- SDWebImage源碼解讀之SDWebImageDownloaderOperation 簡書 博客園
