1 概述
這篇博文將分析SDWebImageDownloader
和SDWebImageDownloaderOperation
糯彬。SDWebImage
通過這兩個(gè)類處理圖片的網(wǎng)絡(luò)加載崔拥。SDWebImageManager
通過屬性imageDownloader
持有SDWebImageDownloader
并且調(diào)用它的downloadImageWithURL
來從網(wǎng)絡(luò)加載圖片。SDWebImageDownloader
實(shí)現(xiàn)了圖片加載的具體處理涵亏,如果圖片在緩存存在則從緩存區(qū),如果緩存不存在蒲凶,則直接創(chuàng)建一個(gè)SDWebImageDownloaderOperation
對(duì)象來下載圖片气筋。管理NSURLRequest對(duì)象請(qǐng)求頭的封裝、緩存旋圆、cookie的設(shè)置宠默。加載選項(xiàng)的處理等功能。管理Operation之間的依賴關(guān)系灵巧。SDWebImageDownloaderOperation
是一個(gè)自定義的并行Operation子類搀矫。這個(gè)類主要實(shí)現(xiàn)了圖片下載的具體操作、以及圖片下載完成以后的圖片解壓縮刻肄、Operation生命周期管理等瓤球。
2 SDWebImageDownloader
分析
SDWebImageDownlaoder
是一個(gè)單列對(duì)象,主要做了如下工作:
- 定義了
SDWebImageDownloaderOptions
這個(gè)枚舉屬性敏弃,通過這個(gè)枚舉屬性來設(shè)置圖片從網(wǎng)絡(luò)加載的不同情況卦羡。 - 定義并管理了
NSURLSession
對(duì)象,通過這個(gè)對(duì)象來做網(wǎng)絡(luò)請(qǐng)求,并且實(shí)現(xiàn)對(duì)象的代理方法绿饵。 - 定義一個(gè)
NSURLRequest
對(duì)象欠肾,并且管理請(qǐng)求頭的拼裝。 - 對(duì)于每一個(gè)網(wǎng)絡(luò)請(qǐng)求,通過一個(gè)
SDWebImageDownloaderOperation
自定義的NSOperation
來操作網(wǎng)絡(luò)下載拟赊。 - 管理網(wǎng)絡(luò)加載過程和完成時(shí)候的回調(diào)工作刺桃。通過
addProgressCallback
實(shí)現(xiàn)。
2.1 SDWebImageDownloaderOptions
枚舉類型
可以通過這個(gè)枚舉類型來控制網(wǎng)絡(luò)加載要门、請(qǐng)求頭虏肾、緩存策略等。
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
SDWebImageDownloaderLowPriority = 1 << 0,
SDWebImageDownloaderProgressiveDownload = 1 << 1,
/*
*默認(rèn)情況下欢搜,http請(qǐng)求阻止使用NSURLCache對(duì)象封豪。如果設(shè)置了這個(gè)標(biāo)記,則NSURLCache會(huì)被http請(qǐng)求使用炒瘟。
*/
SDWebImageDownloaderUseNSURLCache = 1 << 2,
/*
*如果image/imageData是從NSURLCache返回的吹埠。則completion這個(gè)回調(diào)會(huì)返回nil。
*/
SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
/*
*如果app進(jìn)入后臺(tái)模式疮装,是否繼續(xù)下載缘琅。這個(gè)是通過在后臺(tái)申請(qǐng)時(shí)間來完成這個(gè)操作。如果指定的時(shí)間范圍內(nèi)沒有完成廓推,則直接取消下載刷袍。
*/
SDWebImageDownloaderContinueInBackground = 1 << 4,
/*
處理緩存在`NSHTTPCookieStore`對(duì)象里面的cookie。通過設(shè)置`NSMutableURLRequest.HTTPShouldHandleCookies = YES`來實(shí)現(xiàn)的樊展。
*/
SDWebImageDownloaderHandleCookies = 1 << 5,
/*
*允許非信任的SSL證書請(qǐng)求呻纹。
*在測(cè)試的時(shí)候很有用。但是正式環(huán)境要小心使用专缠。
*/
SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,
/*
* 默認(rèn)情況下雷酪,圖片加載的順序是根據(jù)加入隊(duì)列的順序加載的。但是這個(gè)標(biāo)記會(huì)把任務(wù)加入隊(duì)列的最前面涝婉。
*/
SDWebImageDownloaderHighPriority = 1 << 7,
/*
*默認(rèn)情況下哥力,圖片會(huì)按照他的原始大小來解碼顯示。這個(gè)屬性會(huì)調(diào)整圖片的尺寸到合適的大小根據(jù)設(shè)備的內(nèi)存限制墩弯。
*如果`SDWebImageProgressiveDownload`標(biāo)記被設(shè)置了吩跋,則這個(gè)flag不起作用。
*/
SDWebImageDownloaderScaleDownLargeImages = 1 << 8,
};
2.2 SDWebImageDownloader
的屬性和初始化
可以通過它的屬性對(duì)最大并行下載數(shù)量渔工、超時(shí)時(shí)間锌钮、operation之間的下載順序、做處理涨缚。
/**
* 當(dāng)圖片下載完成以后轧粟,加壓縮圖片以后再換成。這樣可以提升性能但是會(huì)占用更多的存儲(chǔ)空間脓魏。
* 模式Y(jié)ES,如果你因?yàn)檫^多的內(nèi)存消耗導(dǎo)致一個(gè)奔潰兰吟,可以把這個(gè)屬性設(shè)置為NO。
*/
@property (assign, nonatomic) BOOL shouldDecompressImages;
/**
最大并行下載的數(shù)量
*/
@property (assign, nonatomic) NSInteger maxConcurrentDownloads;
/**
當(dāng)前并行下載數(shù)量
*/
@property (readonly, nonatomic) NSUInteger currentDownloadCount;
/**
下載超時(shí)時(shí)間設(shè)置
*/
@property (assign, nonatomic) NSTimeInterval downloadTimeout;
/**
改變下載operation的執(zhí)行順序茂翔。默認(rèn)是FIFO混蔼。
*/
@property (assign, nonatomic) SDWebImageDownloaderExecutionOrder executionOrder;
/**
單列方法。返回一個(gè)單列對(duì)象
@return 返回一個(gè)單列的SDWebImageDownloader對(duì)象
*/
+ (nonnull instancetype)sharedDownloader;
/**
為圖片加載request設(shè)置一個(gè)SSL證書對(duì)象珊燎。
*/
@property (strong, nonatomic, nullable) NSURLCredential *urlCredential;
/**
Basic認(rèn)證請(qǐng)求設(shè)置用戶名和密碼
*/
@property (strong, nonatomic, nullable) NSString *username;
@property (strong, nonatomic, nullable) NSString *password;
/**
* 為http請(qǐng)求設(shè)置header惭嚣。
* 每一request執(zhí)行的時(shí)候,這個(gè)Block都會(huì)被執(zhí)行悔政。用于向http請(qǐng)求添加請(qǐng)求域晚吞。
*/
@property (nonatomic, copy, nullable) SDWebImageDownloaderHeadersFilterBlock headersFilter;
/**
初始化一個(gè)請(qǐng)求對(duì)象
@param sessionConfiguration NSURLSessionTask初始化配置
@return 返回一個(gè)SDWebImageDownloader對(duì)象
*/
- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration NS_DESIGNATED_INITIALIZER;
/**
設(shè)置請(qǐng)求頭域
@param value 請(qǐng)求頭域值
@param field 請(qǐng)求頭域名
*/
- (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field;
/*
*獲取請(qǐng)求頭域的值
*/
- (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field;
/**
設(shè)置一個(gè)`SDWebImageDownloaderOperation`的子類作為`NSOperation`來構(gòu)建request來下載一張圖片。
@param operationClass 指定的子類
*/
- (void)setOperationClass:(nullable Class)operationClass;
/**
所有的下載圖片的Operation都加入NSoperationQueue中
*/
@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;
/**
最后一個(gè)添加的Operation
*/
@property (weak, nonatomic, nullable) NSOperation *lastAddedOperation;
/**
自定義的NSOperation子類
*/
@property (assign, nonatomic, nullable) Class operationClass;
/**
用于記錄url和他對(duì)應(yīng)的SDWebImageDownloaderOperation對(duì)象谋国。
*/
@property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, SDWebImageDownloaderOperation *> *URLOperations;
/**
請(qǐng)求頭域字典
*/
@property (strong, nonatomic, nullable) SDHTTPHeadersMutableDictionary *HTTPHeaders;
/**
通過這個(gè)`NSURLSession`創(chuàng)建請(qǐng)求
*/
@property (strong, nonatomic) NSURLSession *session;
2.2 downloadImageWithURL
方法
這個(gè)方法是SDWebImageDownloader
的核心方法槽地。SDWebImageManager
通過這個(gè)方法來實(shí)現(xiàn)圖片從網(wǎng)絡(luò)加載。
/**
新建一個(gè)SDWebImageDownloadOperation對(duì)象來來做具體的下載操作芦瘾。同時(shí)指定緩存策略捌蚊、cookie策略、自定義請(qǐng)求頭域等近弟。
@param url url
@param options 加載選項(xiàng)
@param progressBlock 進(jìn)度progress
@param completedBlock 完成回調(diào)
@return 返回一個(gè)SDWebImageDownloadToken缅糟,用于關(guān)聯(lián)一個(gè)請(qǐng)求
*/
- (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;
NSTimeInterval timeoutInterval = sself.downloadTimeout;
if (timeoutInterval == 0.0) {
timeoutInterval = 15.0;
}
/*
*為了避免可能存在的NSURLCache和SDImageCache同時(shí)緩存。我們默認(rèn)不允許image對(duì)象的NSURLCache對(duì)象祷愉。
具體緩存策略參考http://www.reibang.com/p/855c2c6e761f
*/
NSURLRequestCachePolicy cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
if (options & SDWebImageDownloaderUseNSURLCache) {
if (options & SDWebImageDownloaderIgnoreCachedResponse) {
cachePolicy = NSURLRequestReturnCacheDataDontLoad;
} else {
cachePolicy = NSURLRequestUseProtocolCachePolicy;
}
}
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:cachePolicy timeoutInterval:timeoutInterval];
//使用cookies
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
//使用管道
request.HTTPShouldUsePipelining = YES;
//添加自定義請(qǐng)求頭
if (sself.headersFilter) {
request.allHTTPHeaderFields = sself.headersFilter(url, [sself.HTTPHeaders copy]);
}
else {
request.allHTTPHeaderFields = sself.HTTPHeaders;
}
//初始化一個(gè)自定義NSOperation對(duì)象
SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options];
//是否解壓縮返回的圖片
operation.shouldDecompressImages = sself.shouldDecompressImages;
//指定驗(yàn)證信息
if (sself.urlCredential) {
//SSL驗(yàn)證
operation.credential = sself.urlCredential;
} else if (sself.username && sself.password) {
//Basic驗(yàn)證
operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession];
}
//指定優(yōu)先級(jí)
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
}
//把operatin添加進(jìn)入NSOperationQueue中
[sself.downloadQueue addOperation:operation];
/*
如果是LIFO這種模式窗宦,則需要手動(dòng)指定operation之間的依賴關(guān)系
*/
if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
//如果是LIFO,則讓前面的operation依賴于最新添加的operation
[sself.lastAddedOperation addDependency:operation];
sself.lastAddedOperation = operation;
}
return operation;
}];
}
/**
給下載過程添加進(jìn)度
@param progressBlock 進(jìn)度Block
@param completedBlock 完成Block
@param url url地址
@param createCallback nil
@return 返回SDWebImageDownloadToken谣辞。方便后面取消
*/
- (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, ^{
//看是否當(dāng)前url是否有對(duì)應(yīng)的Operation圖片加載對(duì)象
SDWebImageDownloaderOperation *operation = self.URLOperations[url];
//如果沒有迫摔,則直接創(chuàng)建一個(gè)。
if (!operation) {
//創(chuàng)建一個(gè)operation泥从。并且添加到URLOperation中句占。
operation = createCallback();
self.URLOperations[url] = operation;
__weak SDWebImageDownloaderOperation *woperation = operation;
//設(shè)置operation操作完成以后的回調(diào)
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;
}
如果要取消一個(gè)下載操作,使用cancel
方法來處理
/**
移除一個(gè)圖片加載操作
@param token 通過token來確定操作
*/
- (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];
}
});
}
另外還有NSURLSession
的代理方法躯嫉,這里就不細(xì)講了纱烘。如果興趣可以參考AFNetWorking
源碼分析。
3 SDWebImageDownloaderOperation
分析
SDWebImageDownloaderOperation
是一個(gè)自定義祈餐、并行的NSOperation
子類擂啥。這個(gè)子類主要實(shí)現(xiàn)的功能有:
- 由于只自定義的并行
NSOperation
,所以需要管理executing
,finished
等各種屬性的處理,并且手動(dòng)觸發(fā)KVO帆阳。 - 在
start
(NSOperation
規(guī)定哺壶,沒有為什么)方法里面實(shí)現(xiàn)主要邏輯。 - 在
NSURLSessionTaskDelegate
和NSURLSessionDataDelegate
中處理數(shù)據(jù)的加載,以及進(jìn)度Block的處理山宾。 - 如果
unownedSession
屬性因?yàn)槟撤N原因是nil至扰,則手動(dòng)初始化一個(gè)做網(wǎng)絡(luò)請(qǐng)求。 - 在代理方法中對(duì)認(rèn)證资锰、數(shù)據(jù)拼裝敢课、完成回調(diào)Block做處理。
- 通過發(fā)送
SDWebImageDownloadStopNotification
,SDWebImageDownloadFinishNotification
,SDWebImageDownloadReceiveResponseNotification
,SDWebImageDownloadStartNotification
來通知Operation的狀態(tài)绷杜。
具體完整源碼如下:
NSString *const SDWebImageDownloadStartNotification = @"SDWebImageDownloadStartNotification";
NSString *const SDWebImageDownloadReceiveResponseNotification = @"SDWebImageDownloadReceiveResponseNotification";
NSString *const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNotification";
NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinishNotification";
static NSString *const kProgressCallbackKey = @"progress";
static NSString *const kCompletedCallbackKey = @"completed";
typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
@interface SDWebImageDownloaderOperation ()
/**
回調(diào)Block列表
*/
@property (strong, nonatomic, nonnull) NSMutableArray<SDCallbacksDictionary *> *callbackBlocks;
/**
自定義并行Operation需要管理的兩個(gè)屬性直秆。默認(rèn)是readonly的,我們這里通過聲明改為可修改的鞭盟。方便我們?cè)诤竺娌僮鳌? */
@property (assign, nonatomic, getter = isExecuting) BOOL executing;
@property (assign, nonatomic, getter = isFinished) BOOL finished;
/**
存儲(chǔ)圖片數(shù)據(jù)
*/
@property (strong, nonatomic, nullable) NSMutableData *imageData;
/**
通過SDWebImageDownloader傳過來圾结。所以這里是weak。因?yàn)樗峭ㄟ^SDWebImageDownloader管理的齿诉。
*/
@property (weak, nonatomic, nullable) NSURLSession *unownedSession;
/**
如果unownedSession是nil疫稿,我們需要手動(dòng)創(chuàng)建一個(gè)并且管理他的生命周期和代理方法
*/
@property (strong, nonatomic, nullable) NSURLSession *ownedSession;
/**
dataTask對(duì)象
*/
@property (strong, nonatomic, readwrite, nullable) NSURLSessionTask *dataTask;
/**
一個(gè)并行queue。用于控制數(shù)據(jù)的處理
*/
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t barrierQueue;
#if SD_UIKIT
/**
如果用戶設(shè)置了后臺(tái)繼續(xù)加載選線鹃两。則通過backgroundTask來繼續(xù)下載圖片
*/
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId;
#endif
@end
@implementation SDWebImageDownloaderOperation {
size_t width, height;
#if SD_UIKIT || SD_WATCH
UIImageOrientation orientation;
#endif
}
@synthesize executing = _executing;
@synthesize finished = _finished;
- (nonnull instancetype)init {
return [self initWithRequest:nil inSession:nil options:0];
}
- (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];
//默認(rèn)情況下遗座。_executing和finished都是NO
_executing = NO;
_finished = NO;
_expectedSize = 0;
_unownedSession = session;
_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
}
return self;
}
- (void)dealloc {
SDDispatchQueueRelease(_barrierQueue);
}
/**
給Operation添加進(jìn)度和回調(diào)Block
@param progressBlock 進(jìn)度Block
@param completedBlock 回調(diào)Block
@return 回調(diào)字典
*/
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
//把Operation對(duì)應(yīng)的回調(diào)和進(jìn)度Block存入一個(gè)字典中
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
//把完成和進(jìn)度Block加入callbackBlocks中
dispatch_barrier_async(self.barrierQueue, ^{
[self.callbackBlocks addObject:callbacks];
});
return callbacks;
}
- (nullable NSArray<id> *)callbacksForKey:(NSString *)key {
__block NSMutableArray<id> *callbacks = nil;
dispatch_sync(self.barrierQueue, ^{
// We need to remove [NSNull null] because there might not always be a progress block for each callback
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;
dispatch_barrier_sync(self.barrierQueue, ^{
[self.callbackBlocks removeObjectIdenticalTo:token];
if (self.callbackBlocks.count == 0) {
shouldCancel = YES;
}
});
if (shouldCancel) {
[self cancel];
}
return shouldCancel;
}
/**
并行的Operation需要重寫這個(gè)方法.在這個(gè)方法里面做具體的處理
*/
- (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)];
//如果用戶甚至了Background模式,則設(shè)置一個(gè)backgroundTask
if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
__weak __typeof__ (self) wself = self;
UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
//background結(jié)束以后俊扳。做清理工作
__strong __typeof (wself) sself = wself;
if (sself) {
[sself cancel];
[app endBackgroundTask:sself.backgroundTaskId];
sself.backgroundTaskId = UIBackgroundTaskInvalid;
}
}];
}
#endif
NSURLSession *session = self.unownedSession;
//如果SDWebImageDownloader傳入的session是nil途蒋,則自己手動(dòng)初始化一個(gè)。
if (!self.unownedSession) {
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.
*/
self.ownedSession = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];
session = self.ownedSession;
}
self.dataTask = [session dataTaskWithRequest:self.request];
self.executing = YES;
}
//發(fā)送請(qǐng)求
[self.dataTask resume];
if (self.dataTask) {
//第一次調(diào)用進(jìn)度BLOCK
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
});
} else {
[self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}]];
}
#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
}
/**
如果要取消一個(gè)Operation馋记,就會(huì)調(diào)用這個(gè)方法号坡。
*/
- (void)cancel {
@synchronized (self) {
[self cancelInternal];
}
}
- (void)cancelInternal {
if (self.isFinished) return;
[super cancel];
if (self.dataTask) {
[self.dataTask cancel];
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
});
// As we cancelled the connection, its callback won't be called and thus won't
// maintain the isFinished and isExecuting flags.
//更新狀態(tài)
if (self.isExecuting) self.executing = NO;
if (!self.isFinished) self.finished = YES;
}
[self reset];
}
- (void)done {
self.finished = YES;
self.executing = NO;
[self reset];
}
- (void)reset {
dispatch_barrier_async(self.barrierQueue, ^{
[self.callbackBlocks removeAllObjects];
});
self.dataTask = nil;
self.imageData = nil;
if (self.ownedSession) {
[self.ownedSession invalidateAndCancel];
self.ownedSession = nil;
}
}
/**
需要手動(dòng)觸發(fā)_finished的KVO。這個(gè)是自定義并發(fā)`NSOperation`必須實(shí)現(xiàn)的梯醒。
@param finished 改變狀態(tài)
*/
- (void)setFinished:(BOOL)finished {
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
/**
需要手動(dòng)觸發(fā)_executing的KVO宽堆。這個(gè)是自定義并發(fā)`NSOperation`必須實(shí)現(xiàn)的。
@param executing 改變狀態(tài)
*/
- (void)setExecuting:(BOOL)executing {
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}
/**
返回YES茸习,表明這個(gè)NSOperation對(duì)象是并發(fā)的
@return 返回bool值
*/
- (BOOL)isConcurrent {
return YES;
}
#pragma mark NSURLSessionDataDelegate
/*
*/
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
//'304 Not Modified' is an exceptional one
if (![response respondsToSelector:@selector(statusCode)] || (((NSHTTPURLResponse *)response).statusCode < 400 && ((NSHTTPURLResponse *)response).statusCode != 304)) {
//期望的總長(zhǎng)度
NSInteger expected = response.expectedContentLength > 0 ? (NSInteger)response.expectedContentLength : 0;
self.expectedSize = expected;
//進(jìn)度回調(diào)Block
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, expected, self.request.URL);
}
self.imageData = [[NSMutableData alloc] initWithCapacity:expected];
self.response = response;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:self];
});
}
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.
/*
如果返回304表示圖片么有變化畜隶。在這種情況下,我們只需要取消operation并且返回緩存的圖片就可以了号胚。
*/
if (code == 304) {
[self cancelInternal];
} else {
[self.dataTask cancel];
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
});
[self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:((NSHTTPURLResponse *)response).statusCode userInfo:nil]];
[self done];
}
//這個(gè)表示允許繼續(xù)加載
if (completionHandler) {
completionHandler(NSURLSessionResponseAllow);
}
}
/*
*會(huì)被多次調(diào)用籽慢。獲取圖片數(shù)據(jù)
*/
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
[self.imageData appendData:data];
if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0) {
// The following code is from http://www.cocoaintheshell.com/2011/05/progressive-images-download-imageio/
// Thanks to the author @Nyx0uf
// Get the total bytes downloaded
//獲取已經(jīng)下載的數(shù)據(jù)長(zhǎng)度
const NSInteger totalSize = self.imageData.length;
// Update the data source, we must pass ALL the data, not just the new bytes
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)self.imageData, NULL);
/*
*width和height都是0的話表示還么有獲取到圖片的高度和寬度。我們可以通過數(shù)據(jù)來獲取圖片的寬度和高度
*此時(shí)表示第一次收到圖片數(shù)據(jù)
*/
if (width + height == 0) {
//獲取圖片數(shù)據(jù)的屬性
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL);
if (properties) {
NSInteger orientationValue = -1;
//獲取高度值
CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
if (val) CFNumberGetValue(val, kCFNumberLongType, &height);
//獲取寬度值
val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
if (val) CFNumberGetValue(val, kCFNumberLongType, &width);
//獲取圖片的方向值
val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation);
if (val) CFNumberGetValue(val, kCFNumberNSIntegerType, &orientationValue);
CFRelease(properties);
// When we draw to Core Graphics, we lose orientation information,
// which means the image below born of initWithCGIImage will be
// oriented incorrectly sometimes. (Unlike the image born of initWithData
// in didCompleteWithError.) So save it here and pass it on later.
#if SD_UIKIT || SD_WATCH
orientation = [[self class] orientationFromPropertyValue:(orientationValue == -1 ? 1 : orientationValue)];
#endif
}
}
/*
* 這個(gè)表示已經(jīng)收到部分圖片數(shù)據(jù)并且還么有獲取到所有的圖片數(shù)據(jù)
*/
if (width + height > 0 && totalSize < self.expectedSize) {
// Create the image
CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
#if SD_UIKIT || SD_WATCH
// Workaround for iOS anamorphic image
if (partialImageRef) {
const size_t partialHeight = CGImageGetHeight(partialImageRef);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef bmContext = CGBitmapContextCreate(NULL, width, height, 8, width * 4, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(colorSpace);
if (bmContext) {
CGContextDrawImage(bmContext, (CGRect){.origin.x = 0.0f, .origin.y = 0.0f, .size.width = width, .size.height = partialHeight}, partialImageRef);
CGImageRelease(partialImageRef);
partialImageRef = CGBitmapContextCreateImage(bmContext);
CGContextRelease(bmContext);
}
else {
CGImageRelease(partialImageRef);
partialImageRef = nil;
}
}
#endif
if (partialImageRef) {
#if SD_UIKIT || SD_WATCH
UIImage *image = [UIImage imageWithCGImage:partialImageRef scale:1 orientation:orientation];
#elif SD_MAC
UIImage *image = [[UIImage alloc] initWithCGImage:partialImageRef size:NSZeroSize];
#endif
//獲取圖片url對(duì)應(yīng)的key
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
//根據(jù)原始圖片數(shù)據(jù)獲取對(duì)應(yīng)scale下面的圖片
UIImage *scaledImage = [self scaledImageForKey:key image:image];
//是否解壓縮圖片
if (self.shouldDecompressImages) {
/*
*解壓縮圖片
*/
image = [UIImage decodedImageWithImage:scaledImage];
}
else {
image = scaledImage;
}
CGImageRelease(partialImageRef);
[self callCompletionBlocksWithImage:image imageData:nil error:nil finished:NO];
}
}
CFRelease(imageSource);
}
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(self.imageData.length, self.expectedSize, self.request.URL);
}
}
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
//根據(jù)request的選項(xiàng)猫胁。決定是否緩存NSCachedURLResponse
NSCachedURLResponse *cachedResponse = proposedResponse;
if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) {
// Prevents caching of responses
cachedResponse = nil;
}
if (completionHandler) {
completionHandler(cachedResponse);
}
}
#pragma mark NSURLSessionTaskDelegate
/*
網(wǎng)絡(luò)請(qǐng)求加載完成箱亿,在這里處理獲得的數(shù)據(jù)
*/
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
@synchronized(self) {
self.dataTask = nil;
//發(fā)送圖片下載完成的通知
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
if (!error) {
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:self];
}
});
}
if (error) {
[self callCompletionBlocksWithError:error];
} else {
if ([self callbacksForKey:kCompletedCallbackKey].count > 0) {
if (self.imageData) {
UIImage *image = [UIImage sd_imageWithData:self.imageData];
//獲取url對(duì)應(yīng)的緩存Key
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
image = [self scaledImageForKey:key image:image];
// Do not force decoding animated GIFs
if (!image.images) {
//是否加壓縮圖片數(shù)據(jù)
if (self.shouldDecompressImages) {
//如果設(shè)置了SDWebImageDownloaderScaleDownLargeImages。則返回處理過的圖片
if (self.options & SDWebImageDownloaderScaleDownLargeImages) {
#if SD_UIKIT || SD_WATCH
image = [UIImage decodedAndScaledDownImageWithImage:image];
[self.imageData setData:UIImagePNGRepresentation(image)];
#endif
} else {
image = [UIImage decodedImageWithImage:image];
}
}
}
//構(gòu)建回調(diào)Block
if (CGSizeEqualToSize(image.size, CGSizeZero)) {
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}]];
} else {
[self callCompletionBlocksWithImage:image imageData:self.imageData error:nil finished:YES];
}
} else {
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}]];
}
}
}
[self done];
}
/*
驗(yàn)證HTTPS的證書
*/
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
//使用可信任證書機(jī)構(gòu)的證書
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
//如果SDWebImageDownloaderAllowInvalidSSLCertificates屬性設(shè)置了弃秆,則不驗(yàn)證SSL證書届惋。直接信任
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;
}
}
//驗(yàn)證證書
if (completionHandler) {
completionHandler(disposition, credential);
}
}
#pragma mark Helper methods
#if SD_UIKIT || SD_WATCH
/**
把整數(shù)轉(zhuǎn)換為對(duì)應(yīng)的枚舉值
@param value 整數(shù)值
@return 枚舉值
*/
+ (UIImageOrientation)orientationFromPropertyValue:(NSInteger)value {
switch (value) {
case 1:
return UIImageOrientationUp;
case 3:
return UIImageOrientationDown;
case 8:
return UIImageOrientationLeft;
case 6:
return UIImageOrientationRight;
case 2:
return UIImageOrientationUpMirrored;
case 4:
return UIImageOrientationDownMirrored;
case 5:
return UIImageOrientationLeftMirrored;
case 7:
return UIImageOrientationRightMirrored;
default:
return UIImageOrientationUp;
}
}
#endif
/**
* 通過image對(duì)象獲取對(duì)應(yīng)scale模式下的圖像
*/
- (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image {
return SDScaledImageForKey(key, image);
}
- (BOOL)shouldContinueWhenAppEntersBackground {
return self.options & SDWebImageDownloaderContinueInBackground;
}
- (void)callCompletionBlocksWithError:(nullable NSError *)error {
[self callCompletionBlocksWithImage:nil imageData:nil error:error finished:YES];
}
/**
處理回調(diào)
@param image UIImage數(shù)據(jù)
@param imageData Image的data數(shù)據(jù)
@param error 錯(cuò)誤
@param finished 是否完成的標(biāo)記位
*/
- (void)callCompletionBlocksWithImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
error:(nullable NSError *)error
finished:(BOOL)finished {
//獲取key對(duì)應(yīng)的回調(diào)Block數(shù)組
NSArray<id> *completionBlocks = [self callbacksForKey:kCompletedCallbackKey];
dispatch_main_async_safe(^{
//調(diào)用回調(diào)
for (SDWebImageDownloaderCompletedBlock completedBlock in completionBlocks) {
completedBlock(image, imageData, error, finished);
}
});
}
@end