4>SDWebImageDownloader
SDWebImageDownloader是以單例存在骤视,對圖片下載管理烤芦,進行一些全局的配置。如下:
1).設(shè)置最大并發(fā)數(shù)馒稍,下載時間默認15秒,是否壓縮圖片和下載順序等浅侨。
2).設(shè)置operation的請求頭信息纽谒,負責(zé)生成單個SDWebImageDownloaderOperation以及取消operation等。
3).設(shè)置下載的策略SDWebImageDownloaderOptions如输。
SDWebImageDownloaderOptions
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
//圖片下載設(shè)置低優(yōu)先級
SDWebImageDownloaderLowPriority = 1 << 0,
//分進度下載圖片
SDWebImageDownloaderProgressiveDownload = 1 << 1,
//設(shè)置了該選項則使用NSURLCache緩存策略
SDWebImageDownloaderUseNSURLCache = 1 << 2,
//如果圖片從NSURLCache讀取鼓黔,則回到block中的image和imageData置為nil
SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
//在iOS4之后,如果設(shè)置了此選項則在后臺依然下載圖片挨决,申請額外的時間來進行后臺下載
SDWebImageDownloaderContinueInBackground = 1 << 4,
// 處理存儲在NSHTTPCookieStore中的cookies
SDWebImageDownloaderHandleCookies = 1 << 5,
//允許非信任的SSL證書
SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,
//設(shè)置圖片下載高優(yōu)先級
SDWebImageDownloaderHighPriority = 1 << 7,
//對大圖片進行縮放
SDWebImageDownloaderScaleDownLargeImages = 1 << 8,
};
SDWebImageDownloaderExecutionOrder
SDWebImageDownloaderExecutionOrder是下載執(zhí)行的順序:
typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
//按照隊列先進先出的順序下載
SDWebImageDownloaderFIFOExecutionOrder,
//按照棧后進先出進行下載
SDWebImageDownloaderLIFOExecutionOrder
};
SDWebImageDownloadToken
是作為下載操作的唯一標(biāo)識请祖,在創(chuàng)建operation的時候初始化綁定,當(dāng)需要去cancel操作的時候就需要這個token脖祈。
@interface SDWebImageDownloadToken : NSObject
@property (nonatomic, strong, nullable) NSURL *url;
@property (nonatomic, strong, nullable) id downloadOperationCancelToken;
@end
SDWebImageDownloader.h屬性方法:
@interface SDWebImageDownloader : NSObject
//是否壓縮圖片
@property (assign, nonatomic) BOOL shouldDecompressImages;
//設(shè)置最大并發(fā)數(shù)量
@property (assign, nonatomic) NSInteger maxConcurrentDownloads;
//獲取當(dāng)前下載的數(shù)量
@property (readonly, nonatomic) NSUInteger currentDownloadCount;
//下載時間 默認15秒
@property (assign, nonatomic) NSTimeInterval downloadTimeout;
//NSURLSession配置一些請求所需要的策略
@property (readonly, nonatomic, nonnull) NSURLSessionConfiguration *sessionConfiguration;
//設(shè)置下載順序
@property (assign, nonatomic) SDWebImageDownloaderExecutionOrder executionOrder;
//生成單例 并返回當(dāng)前實例
+ (nonnull instancetype)sharedDownloader;
//設(shè)置身份認證
@property (strong, nonatomic, nullable) NSURLCredential *urlCredential;
//設(shè)置用戶名
@property (strong, nonatomic, nullable) NSString *username;
//設(shè)置密碼
@property (strong, nonatomic, nullable) NSString *password;
//針對header進行過濾的block
@property (nonatomic, copy, nullable) SDWebImageDownloaderHeadersFilterBlock headersFilter;
//生成一個實例肆捕,利用特定的配置sessionConfiguration
- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration NS_DESIGNATED_INITIALIZER;
//針對request添加HTTP header
- (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field;
//返回某個header field的value
- (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field;
//設(shè)置一個SDWebImageDownloaderOperation的子類賦值給_operationClass
- (void)setOperationClass:(nullable Class)operationClass;
//根據(jù)指定的url異步加載圖片
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
//取消指定token的下載
- (void)cancel:(nullable SDWebImageDownloadToken *)token;
//設(shè)置下載隊列是否掛起
- (void)setSuspended:(BOOL)suspended;
//取消所有的下載
- (void)cancelAllDownloads;
//強制給self設(shè)置一個新的NSURLSession
- (void)createNewSessionWithConfiguration:(nonnull NSURLSessionConfiguration *)sessionConfiguration;
//取消operation并且session設(shè)置為Invalidates
- (void)invalidateSessionAndCancel:(BOOL)cancelPendingOperations;
@end
SDWebImageDownloader.m方法:
@interface SDWebImageDownloader () <NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
//定義下載隊列
@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;
//定義下載的上個operation 作用是為了后面的下載依賴
@property (weak, nonatomic, nullable) NSOperation *lastAddedOperation;
// 圖片下載操作類
@property (assign, nonatomic, nullable) Class operationClass;
//下載url作為key value是具體的下載operation 用字典來存儲,方便cancel等操作
@property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, SDWebImageDownloaderOperation *> *URLOperations;
//HTTP請求頭
@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
//定義隊列用來保證處理所有下載操作線程安全盖高,將任務(wù)利用dispatch_barrier_sync隊列barrierQueue
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t barrierQueue;
//利用NSURLSession進行網(wǎng)絡(luò)請求
@property (strong, nonatomic) NSURLSession *session;
@end
SDWebImageDownloader.m的關(guān)鍵方法:
//這里的initialize就是開篇講到的知識點慎陵,不過這里還有一個值得學(xué)習(xí)的點就是NSClassFromString,用來判斷當(dāng)前程序是否有指定字符串的類喻奥,如果沒有回返回一個空對象席纽,id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")];這樣配合對一個不確定進行初始化
+ (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];
}
}
在- (nonnull instancetype)init;和- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration;方法中進行初始化操作,不展開說明撞蚕。
downloadImageWithURL方法如下:
//給定指定URL下載圖片
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
__weak SDWebImageDownloader *wself = self;
//在下面的block中創(chuàng)建operation
return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^SDWebImageDownloaderOperation *{
__strong __typeof (wself) sself = wself;
//設(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
NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
//創(chuàng)建request 設(shè)置請求緩存策略 下載時間
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
cachePolicy:cachePolicy
timeoutInterval:timeoutInterval];
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
//HTTPShouldUsePipelining設(shè)置為YES, 則允許不必等到response, 就可以再次請求. 這個會很大的提高網(wǎng)絡(luò)請求的效率,但是也可能會出問題
//因為客戶端無法正確的匹配請求與響應(yīng), 所以這依賴于服務(wù)器必須保證,響應(yīng)的順序與客戶端請求的順序一致.如果服務(wù)器不能保證這一點, 那可能導(dǎo)致響應(yīng)和請求混亂.
request.HTTPShouldUsePipelining = YES;
//設(shè)置請求頭
if (sself.headersFilter) {
request.allHTTPHeaderFields = sself.headersFilter(url, [sself.HTTPHeaders copy]);
}
else {
request.allHTTPHeaderFields = sself.HTTPHeaders;
}
//重頭戲 在這里創(chuàng)建operation對象
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) {
operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession];
}
//下載優(yōu)先級
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
}
//添加operation到下載隊列
[sself.downloadQueue addOperation:operation];
//設(shè)置下載的順序 是按照隊列還是棧
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;
}];
}
//包裝callbackBlocks润梯,URLOperations和token
- (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;
//生成URLOperations字典 下載url作為key value是具體的下載operation 方便下載cancel等操作
dispatch_barrier_sync(self.barrierQueue, ^{
SDWebImageDownloaderOperation *operation = self.URLOperations[url];
if (!operation) {
operation = createCallback();
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和下載結(jié)束completedBlock封裝成字典SDCallbacksDictionary,裝入數(shù)組callbackBlocks甥厦,
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
//這里生成token標(biāo)識纺铭,這個token說白了就是為了在SDWebImageManager中調(diào)用[self.imageDownloader cancel:subOperationToken];來做取消的
token = [SDWebImageDownloadToken new];
token.url = url;
token.downloadOperationCancelToken = downloadOperationCancelToken;
});
return token;
}
SDWebImageDownloader中的其他方法都是比較簡單的例如下面,不單獨拿出來講刀疙,大家看一眼就懂了舶赔。
- (void)setSuspended:(BOOL)suspended;
- (void)cancelAllDownloads ;
- (NSInteger)maxConcurrentDownloads
>5 SDWebImageDownloaderOperation
SDWebImageDownloaderOperation繼承自NSOperation,是具體的執(zhí)行圖片下載的單位谦秧。負責(zé)生成NSURLSessionTask進行圖片請求竟纳,支持下載取消和后臺下載,在下載中及時匯報下載進度疚鲤,在下載成功后锥累,對圖片進行解碼,縮放和壓縮等操作集歇。
SDWebImageDownloaderOperation.h文件的具體注解:
//開始下載通知
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStartNotification;
//收到response通知
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadReceiveResponseNotification;
//停止下載通知
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStopNotification;
//結(jié)束下載通知
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadFinishNotification;
//如果你想要使用一個自定義的downloader對象桶略,那么這個對象需要繼承自NSOperation,并且實現(xiàn)這個協(xié)議
@protocol SDWebImageDownloaderOperationInterface<NSObject>
//SDWebImageDownloaderOperationInterface的初始化方法
- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
inSession:(nullable NSURLSession *)session
options:(SDWebImageDownloaderOptions)options;
//綁定DownloaderOperation的下載進度block和結(jié)束block
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
//返回當(dāng)前下載操作的圖片是否應(yīng)該壓縮
- (BOOL)shouldDecompressImages;
//設(shè)置是否應(yīng)該壓縮圖片
- (void)setShouldDecompressImages:(BOOL)value;
//返回身份認證
- (nullable NSURLCredential *)credential;
//設(shè)置身份認證
- (void)setCredential:(nullable NSURLCredential *)value;
@end
@interface SDWebImageDownloaderOperation : NSOperation <SDWebImageDownloaderOperationInterface, SDWebImageOperation, NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
//operation的請求request
@property (strong, nonatomic, readonly, nullable) NSURLRequest *request;
//operation的task
@property (strong, nonatomic, readonly, nullable) NSURLSessionTask *dataTask;
//圖片是否可以被壓縮屬性
@property (assign, nonatomic) BOOL shouldDecompressImages;
/**
* Was used to determine whether the URL connection should consult the credential storage for authenticating the connection.
* @deprecated Not used for a couple of versions
*/
@property (nonatomic, assign) BOOL shouldUseCredentialStorage __deprecated_msg("Property deprecated. Does nothing. Kept only for backwards compatibility");
//身份認證
@property (nonatomic, strong, nullable) NSURLCredential *credential;
//下載配置策略
@property (assign, nonatomic, readonly) SDWebImageDownloaderOptions options;
//期望data的大小size
@property (assign, nonatomic) NSInteger expectedSize;
//operation的response
@property (strong, nonatomic, nullable) NSURLResponse *response;
//初始化一個SDWebImageDownloaderOperation對象
- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
inSession:(nullable NSURLSession *)session
//綁定DownloaderOperation的下載進度block和結(jié)束block
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
//SDWebImageOperation的協(xié)議方法
- (BOOL)cancel:(nullable id)token;
@end
SDWebImageDownloaderOperation.m的具體注解文件:
//開始下載通知賦值
NSString *const SDWebImageDownloadStartNotification = @"SDWebImageDownloadStartNotification";
//收到response通知賦值
NSString *const SDWebImageDownloadReceiveResponseNotification = @"SDWebImageDownloadReceiveResponseNotification";
//停止下載通知賦值
NSString *const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNotification";
//結(jié)束下載通知賦值
NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinishNotification";
//靜態(tài)全局變量作為下載進度block字典存儲的key
static NSString *const kProgressCallbackKey = @"progress";
//靜態(tài)全局變量作為結(jié)束下載block字典存儲的key
static NSString *const kCompletedCallbackKey = @"completed";
//聲明類型SDCallbacksDictionary key=NSString,value=id
typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
@interface SDWebImageDownloaderOperation ()
@property (strong, nonatomic, nonnull) NSMutableArray<SDCallbacksDictionary *> *callbackBlocks;
//是否正在執(zhí)行
@property (assign, nonatomic, getter = isExecuting) BOOL executing;
//是否下載結(jié)束
@property (assign, nonatomic, getter = isFinished) BOOL finished;
//圖片的imageData
@property (strong, nonatomic, nullable) NSMutableData *imageData;
//緩存的data
@property (copy, nonatomic, nullable) NSData *cachedData;
//網(wǎng)絡(luò)請求session删性,注意這里使用的屬性修飾符是weak亏娜,因為unownedSession是從SDWebImageDownloader傳過來的,所以需要SDWebImageDownloader管理
@property (weak, nonatomic, nullable) NSURLSession *unownedSession;
//網(wǎng)絡(luò)請求session蹬挺,注意這里使用的屬性修飾符是strong维贺,如果unownedSession是nil,我們需要手動創(chuàng)建一個并且管理它的生命周期和代理方法
@property (strong, nonatomic, nullable) NSURLSession *ownedSession;
//網(wǎng)絡(luò)請求具體的task
@property (strong, nonatomic, readwrite, nullable) NSURLSessionTask *dataTask;
//全局并行隊列巴帮,控制數(shù)據(jù)
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t barrierQueue;
#if SD_UIKIT
//如果用戶設(shè)置了后臺繼續(xù)加載選項溯泣,通過backgroundTask來繼續(xù)下載圖片
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId;
#endif
//圖片解碼器耘擂,有意思的是如果圖片沒有完全下載完成時也可以解碼展示部分圖片
@property (strong, nonatomic, nullable) id<SDWebImageProgressiveCoder> progressiveCoder;
@end
@implementation SDWebImageDownloaderOperation
@synthesize executing = _executing;
@synthesize finished = _finished;
//init方法
- (nonnull instancetype)init {
return [self initWithRequest:nil inSession:nil options:0];
}
//初始化方法狈涮,進行一些基礎(chǔ)數(shù)據(jù)的賦值配置
- (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;
_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
}
return self;
}
- (void)dealloc {
SDDispatchQueueRelease(_barrierQueue);
}
//綁定DownloaderOperation的下載進度block和結(jié)束block
- (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];
//使用dispatch_barrier_async方法異步方式不阻塞當(dāng)前線程,串行執(zhí)行添加進數(shù)組的操作
dispatch_barrier_async(self.barrierQueue, ^{
[self.callbackBlocks addObject:callbacks];
});
return callbacks;
}
//根據(jù)key獲取回調(diào)塊數(shù)組中所有對應(yīng)key的回調(diào)塊
- (nullable NSArray<id> *)callbacksForKey:(NSString *)key {
__block NSMutableArray<id> *callbacks = nil;
//同步方式執(zhí)行坤学,阻塞當(dāng)前線程也阻塞隊列
dispatch_sync(self.barrierQueue, ^{
// We need to remove [NSNull null] because there might not always be a progress block for each callback
//我們需要刪除數(shù)組中的[NSNull null]元素用押,因為可能callback沒有回調(diào)塊
callbacks = [[self.callbackBlocks valueForKey:key] mutableCopy];
[callbacks removeObjectIdenticalTo:[NSNull null]];
});
return [callbacks copy]; // strip mutability here
}
//取消方法
- (BOOL)cancel:(nullable id)token {
__block BOOL shouldCancel = NO;
//同步方法阻塞當(dāng)前隊列和當(dāng)前線程
dispatch_barrier_sync(self.barrierQueue, ^{
//刪除數(shù)組中回調(diào)塊數(shù)組中的token對象肢簿,token是key為string,value是block的字典
[self.callbackBlocks removeObjectIdenticalTo:token];
//判斷數(shù)組是否為0.則取消下載任務(wù)
if (self.callbackBlocks.count == 0) {
shouldCancel = YES;
}
});
if (shouldCancel) {
[self cancel];
}
return shouldCancel;
}
//重寫operation的start方法蜻拨,當(dāng)任務(wù)添加到NSOperationQueue后會執(zhí)行該方法池充,啟動下載任務(wù)
- (void)start {
//添加同步鎖,防止多線程數(shù)據(jù)競爭
@synchronized (self) {
if (self.isCancelled) {
self.finished = YES;
[self reset];
return;
}
#if SD_UIKIT
//如調(diào)用者配置了在后臺可以繼續(xù)下載圖片缎讼,那么在這里繼續(xù)下載
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
//獲取網(wǎng)絡(luò)請求的緩存數(shù)據(jù)
if (self.options & SDWebImageDownloaderIgnoreCachedResponse) {
// Grab the cached data for later check
NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:self.request];
if (cachedResponse) {
self.cachedData = cachedResponse.data;
}
}
NSURLSession *session = self.unownedSession;
//判斷unownedSession是否為了nil收夸,如果是nil則重新創(chuàng)建一個ownedSession
if (!self.unownedSession) {
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
//超時時間15s
sessionConfig.timeoutIntervalForRequest = 15;
//delegateQueue為nil,所以回調(diào)方法默認在一個子線程的串行隊列中執(zhí)行
self.ownedSession = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];
session = self.ownedSession;
}
//使用session來創(chuàng)建一個NSURLSessionDataTask類型下載任務(wù)
self.dataTask = [session dataTaskWithRequest:self.request];
self.executing = YES;
}
//開始執(zhí)行任務(wù)
[self.dataTask resume];
if (self.dataTask) {
//任務(wù)開啟血崭,遍歷進度塊數(shù)組 執(zhí)行第一個下載進度 進度為0
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
}
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
//在主線程發(fā)送通知卧惜,并將self傳出去,為什么在主線程發(fā)送呢夹纫?
//因為在哪個線程發(fā)送通知就在哪個線程接受通知咽瓷,這樣如果在接受通知方法中執(zhí)行修改UI的操作也不會有問題
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:weakSelf];
});
} else {
//如果創(chuàng)建tataTask失敗就執(zhí)行失敗的回調(diào)塊
[self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}]];
}
#if SD_UIKIT
//后臺繼續(xù)下載
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
}
//SDWebImageOperation的協(xié)議方法
- (void)cancel {
@synchronized (self) {
[self cancelInternal];
}
}
//取消下載
- (void)cancelInternal {
if (self.isFinished) return;
[super cancel];
//如果下載圖片的任務(wù)仍在 則立即取消cancel,并且發(fā)送結(jié)束下載的通知
if (self.dataTask) {
[self.dataTask cancel];
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf];
});
// As we cancelled the connection, its callback won't be called and thus won't
// maintain the isFinished and isExecuting flags.
if (self.isExecuting) self.executing = NO;
if (!self.isFinished) self.finished = YES;
}
[self reset];
}
//下載完成后調(diào)用的方法
- (void)done {
self.finished = YES;
self.executing = NO;
[self reset];
}
//重新設(shè)置數(shù)據(jù)的方法
- (void)reset {
__weak typeof(self) weakSelf = self;
//刪除回調(diào)塊字典數(shù)組的所有元素
dispatch_barrier_async(self.barrierQueue, ^{
[weakSelf.callbackBlocks removeAllObjects];
});
self.dataTask = nil;
NSOperationQueue *delegateQueue;
//如果unownedSession存在就從它里面獲取delegateQueue捷凄,否則從ownedSession獲取
if (self.unownedSession) {
delegateQueue = self.unownedSession.delegateQueue;
} else {
delegateQueue = self.ownedSession.delegateQueue;
}
//如果存在代理方法執(zhí)行隊列
if (delegateQueue) {
//delegateQueue必須是串行隊列 最大并發(fā)量為1
NSAssert(delegateQueue.maxConcurrentOperationCount == 1, @"NSURLSession delegate queue should be a serial queue");
[delegateQueue addOperationWithBlock:^{
weakSelf.imageData = nil;
}];
}
//如果ownedSession存在忱详,則手動調(diào)用invalidateAndCancel進行任務(wù)
if (self.ownedSession) {
[self.ownedSession invalidateAndCancel];
self.ownedSession = nil;
}
}
//finished屬性的stter
- (void)setFinished:(BOOL)finished {
//手動觸發(fā)KVO通知
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
//executing屬性的stter
- (void)setExecuting:(BOOL)executing {
//手動觸發(fā)KVO通知
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}
//重寫NSOperation方法围来,標(biāo)識這是一個并發(fā)任務(wù)
- (BOOL)isConcurrent {
return YES;
}
#pragma mark NSURLSessionDataDelegate
//收到服務(wù)端響應(yīng)跺涤,在一次請求中只會執(zhí)行一次
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
//'304 Not Modified' is an exceptional one
//根據(jù)狀態(tài)碼來判斷請求的狀態(tài)
if (![response respondsToSelector:@selector(statusCode)] || (((NSHTTPURLResponse *)response).statusCode < 400 && ((NSHTTPURLResponse *)response).statusCode != 304)) {
//獲取要下載圖片的長度
NSInteger expected = (NSInteger)response.expectedContentLength;
expected = expected > 0 ? expected : 0;
//設(shè)置當(dāng)前expectedSize
self.expectedSize = expected;
//遍歷進度回調(diào)塊并觸發(fā)進度回調(diào)塊
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, expected, self.request.URL);
}
//根據(jù)response返回的文件大小創(chuàng)建可變data
self.imageData = [[NSMutableData alloc] initWithCapacity:expected];
self.response = response;
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
//在主線程發(fā)送接受response的通知
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:weakSelf];
});
} else {
//進入失敗的情況
NSUInteger code = ((NSHTTPURLResponse *)response).statusCode;
//This is the case when server returns '304 Not Modified'. It means that remote image is not changed.
//In case of 304 we need just cancel the operation and return cached image from the cache.
//如果code等于304直接取消下載,否則執(zhí)行URLSession:task:didCompleteWithError:代理方法
if (code == 304) {
[self cancelInternal];
} else {
[self.dataTask cancel];
}
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
///主線程發(fā)送結(jié)束下載的通知
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf];
});
//執(zhí)行下載失敗異常的代碼塊
[self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:((NSHTTPURLResponse *)response).statusCode userInfo:nil]];
//執(zhí)行一些結(jié)束下載的操作
[self done];
}
if (completionHandler) {
completionHandler(NSURLSessionResponseAllow);
}
}
//每次收到數(shù)據(jù)都會觸發(fā) 可能是多次
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
//向可變數(shù)據(jù)中添加接收到的數(shù)據(jù)
[self.imageData appendData:data];
//如果調(diào)用者配置了需要支持progressive下載监透,即展示已經(jīng)下載的部分桶错,并expectedSize返回的圖片size大于0
if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0) {
// Get the image data
NSData *imageData = [self.imageData copy];
// Get the total bytes downloaded
const NSInteger totalSize = imageData.length;
// Get the finish status
//判斷是否已經(jīng)下載完成
BOOL finished = (totalSize >= self.expectedSize);
//如果不存在解壓對象就去創(chuàng)建一個新的
if (!self.progressiveCoder) {
// We need to create a new instance for progressive decoding to avoid conflicts
for (id<SDWebImageCoder>coder in [SDWebImageCodersManager sharedInstance].coders) {
if ([coder conformsToProtocol:@protocol(SDWebImageProgressiveCoder)] &&
[((id<SDWebImageProgressiveCoder>)coder) canIncrementallyDecodeFromData:imageData]) {
self.progressiveCoder = [[[coder class] alloc] init];
break;
}
}
}
//將imageData轉(zhuǎn)化為image
UIImage *image = [self.progressiveCoder incrementallyDecodedImageWithData:imageData finished:finished];
if (image) {
//通過URL獲取緩存的key
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
//對圖片進行縮放
image = [self scaledImageForKey:key image:image];
//如果調(diào)用者選擇了壓縮圖片,那么在這里執(zhí)行圖片壓縮胀蛮,這里注意院刁,傳入的data是一個**,指向指針的指針粪狼,要用&data表示
if (self.shouldDecompressImages) {
image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&data options:@{SDWebImageCoderScaleDownLargeImagesKey: @(NO)}];
}
//進行失敗回調(diào)
[self callCompletionBlocksWithImage:image imageData:nil error:nil finished:NO];
}
}
//進行下載進度的代碼塊回調(diào)
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(self.imageData.length, self.expectedSize, self.request.URL);
}
}
//如果需要緩存response則做一些處理
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
NSCachedURLResponse *cachedResponse = proposedResponse;
if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) {
// Prevents caching of responses
cachedResponse = nil;
}
if (completionHandler) {
completionHandler(cachedResponse);
}
}
#pragma mark NSURLSessionTaskDelegate
//下載完成或下載失敗時的回調(diào)方法
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
@synchronized(self) {
self.dataTask = nil;
__weak typeof(self) weakSelf = self;
//回到主線程發(fā)送下載結(jié)束的通知
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf];
if (!error) {
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:weakSelf];
}
});
}
//下載失敗則走失敗的回調(diào)
if (error) {
[self callCompletionBlocksWithError:error];
} else {
//判斷下載回調(diào)結(jié)束塊數(shù)量是否大于0
if ([self callbacksForKey:kCompletedCallbackKey].count > 0) {
/**
* If you specified to use `NSURLCache`, then the response you get here is what you need.
*/
NSData *imageData = [self.imageData copy];
//如果圖片存在
if (imageData) {
/** if you specified to only use cached data via `SDWebImageDownloaderIgnoreCachedResponse`,
* then we should check if the cached data is equal to image data
*/
//判斷調(diào)用是否配置了使用緩存退腥,如果是則判斷緩存data是否和當(dāng)前圖片一致任岸,如果一致則回調(diào)結(jié)束
if (self.options & SDWebImageDownloaderIgnoreCachedResponse && [self.cachedData isEqualToData:imageData]) {
// call completion block with nil
[self callCompletionBlocksWithImage:nil imageData:nil error:nil finished:YES];
} else {
//對圖片data解碼
UIImage *image = [[SDWebImageCodersManager sharedInstance] decodedImageWithData:imageData];
//根據(jù)圖片url獲得緩存的key
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
//對圖片進行縮放
image = [self scaledImageForKey:key image:image];
BOOL shouldDecode = YES;
// Do not force decoding animated GIFs and WebPs
//針對GIF和WebP圖片不做壓縮
if (image.images) {
shouldDecode = NO;
} else {
#ifdef SD_WEBP
//sd_imageFormatForImageData分類方法判斷是哪種圖片格式
SDImageFormat imageFormat = [NSData sd_imageFormatForImageData:imageData];
if (imageFormat == SDImageFormatWebP) {
shouldDecode = NO;
}
#endif
}
//如果是可以壓縮的格式,并且調(diào)用者也設(shè)置了壓縮狡刘,那么這里開始壓縮圖片
if (shouldDecode) {
if (self.shouldDecompressImages) {
BOOL shouldScaleDown = self.options & SDWebImageDownloaderScaleDownLargeImages;
image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&imageData options:@{SDWebImageCoderScaleDownLargeImagesKey: @(shouldScaleDown)}];
}
}
if (CGSizeEqualToSize(image.size, CGSizeZero)) {
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}]];
} else {
[self callCompletionBlocksWithImage:image imageData:imageData error:nil finished:YES];
}
}
} else {
//不存在圖片直接回調(diào)結(jié)束
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}]];
}
}
}
//結(jié)束后的操作
[self done];
}
//針對服務(wù)器返回的證書進行處理, 需要在該方法中告訴系統(tǒng)是否需要安裝服務(wù)器返回的證書
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
// 判斷服務(wù)器返回的證書是否是服務(wù)器信任的
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if (!(self.options & SDWebImageDownloaderAllowInvalidSSLCertificates)) {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
} else {
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
disposition = NSURLSessionAuthChallengeUseCredential;
}
} else {
if (challenge.previousFailureCount == 0) {
if (self.credential) {
credential = self.credential;
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
#pragma mark Helper methods
//內(nèi)部調(diào)用一個inline函數(shù)
- (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image {
return SDScaledImageForKey(key, image);
}
//判斷是否支持后臺下載
- (BOOL)shouldContinueWhenAppEntersBackground {
return self.options & SDWebImageDownloaderContinueInBackground;
}
//回調(diào)block
- (void)callCompletionBlocksWithError:(nullable NSError *)error {
[self callCompletionBlocksWithImage:nil imageData:nil error:error finished:YES];
}
//遍歷callbacks代碼塊數(shù)組
- (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);
}
});
}
@end
具體的下載流程用文字來敘述:
1.首先生成繼承自NSOperation的SDWebImageDownloaderOperation享潜,配置當(dāng)前operation。
2.將operation添加到NSOperationQueue下載隊列中嗅蔬,添加到下載隊列會觸發(fā)operation的start方法剑按。
3.如果發(fā)現(xiàn)operation的isCancelled為YES,說明已經(jīng)被取消澜术,則finished=YES結(jié)束下載艺蝴。
4.創(chuàng)建NSURLSessionTask執(zhí)行resume開始下載。
5.當(dāng)收到服務(wù)端的相應(yīng)時根據(jù)code判斷請求狀態(tài)鸟废,如果是正常狀態(tài)則發(fā)送正在接受response的通知以及下載進度猜敢。如果是304或者其他異常狀態(tài)則cancel下載操作。
6.在didReceiveData每次收到服務(wù)器的返回response時盒延,給可變data追加圖片當(dāng)前下載的data锣枝,并匯報下載的進度。
7.在didCompleteWithError下載結(jié)束時兰英,如果下載成功進行圖片data解碼撇叁,圖片的縮放或者壓縮操作,發(fā)送下載結(jié)束通知畦贸。下載失敗執(zhí)行失敗回調(diào)陨闹。
博主水平有限趋厉,難免出現(xiàn)紕漏,如有問題還請不吝賜教胶坠。