前言
CSDN地址:http://blog.csdn.net/game3108/article/details/52598835
本文的中文注釋代碼demo更新在我的github上毛萌。
上篇文章講解的了SDWebImage的Cache部分晃痴,這篇講講一下Download部分缀蹄。
Download
Download部分主要包含如下2個(gè)方法
- SDWebImageDownloader
圖片下載控制類 - SDWebImageDownloaderOperation
NSOperation子類
SDWebImageDownloader
SDWebImageDownloader主要包含以下內(nèi)容:
- 初始化信息
- 相關(guān)請(qǐng)求信息設(shè)置與獲取
- 請(qǐng)求圖片方法
- NSURLSession相關(guān)回調(diào)
初始化信息
SDWebImageDownloader.h中的property聲明
//下載完成執(zhí)行順序
typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
//先進(jìn)先出
SDWebImageDownloaderFIFOExecutionOrder,
//先進(jìn)后出
SDWebImageDownloaderLIFOExecutionOrder
};
@interface SDWebImageDownloader : NSObject
//是否壓縮圖片
@property (assign, nonatomic) BOOL shouldDecompressImages;
//最大下載數(shù)量
@property (assign, nonatomic) NSInteger maxConcurrentDownloads;
//當(dāng)前下載數(shù)量
@property (readonly, nonatomic) NSUInteger currentDownloadCount;
//下載超時(shí)時(shí)間
@property (assign, nonatomic) NSTimeInterval downloadTimeout;
//下載策略
@property (assign, nonatomic) SDWebImageDownloaderExecutionOrder executionOrder;
SDWebImageDownloader.m中的Extension
@interface SDWebImageDownloader () <NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
//下載的queue
@property (strong, nonatomic) NSOperationQueue *downloadQueue;
//上一個(gè)添加的操作
@property (weak, nonatomic) NSOperation *lastAddedOperation;
//操作類
@property (assign, nonatomic) Class operationClass;
//url請(qǐng)求緩存
@property (strong, nonatomic) NSMutableDictionary *URLCallbacks;
//請(qǐng)求頭
@property (strong, nonatomic) NSMutableDictionary *HTTPHeaders;
// This queue is used to serialize the handling of the network responses of all the download operation in a single queue
//這個(gè)queue為了能否序列化處理所有網(wǎng)絡(luò)結(jié)果的返回
@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;
// The session in which data tasks will run
//數(shù)據(jù)runsession
@property (strong, nonatomic) NSURLSession *session;
@end
相關(guān)方法實(shí)現(xiàn)如下:
//判斷用了SDNetworkActivityIndicator藻雌,去替換該方法的內(nèi)容
+ (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];
}
}
//單例
+ (SDWebImageDownloader *)sharedDownloader {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}
//初始化方法
- (id)init {
if ((self = [super init])) {
_operationClass = [SDWebImageDownloaderOperation class];
_shouldDecompressImages = YES;
_executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
_downloadQueue = [NSOperationQueue new];
_downloadQueue.maxConcurrentOperationCount = 6;
_downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
_URLCallbacks = [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;
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.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.
*/
//session初始化
self.session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];
}
return self;
}
- (void)dealloc {
//停止session
[self.session invalidateAndCancel];
self.session = nil;
//停止所有downloadqueue
[self.downloadQueue cancelAllOperations];
SDDispatchQueueRelease(_barrierQueue);
}
從這邊也可以看出匙握,SDWebImageDownloader的下載方法就是用NSOperation。使用NSOperationQueue去控制最大操作數(shù)量和取消所有操作涎才,在NSOperation中運(yùn)行NSUrlSession方法請(qǐng)求參數(shù)溪食。
相關(guān)請(qǐng)求信息設(shè)置與獲取
相關(guān)方法如下:
//設(shè)置hedaer頭
- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field {
if (value) {
self.HTTPHeaders[field] = value;
}
else {
[self.HTTPHeaders removeObjectForKey:field];
}
}
- (NSString *)valueForHTTPHeaderField:(NSString *)field {
return self.HTTPHeaders[field];
}
//設(shè)置多大并發(fā)數(shù)量
- (void)setMaxConcurrentDownloads:(NSInteger)maxConcurrentDownloads {
_downloadQueue.maxConcurrentOperationCount = maxConcurrentDownloads;
}
- (NSUInteger)currentDownloadCount {
return _downloadQueue.operationCount;
}
- (NSInteger)maxConcurrentDownloads {
return _downloadQueue.maxConcurrentOperationCount;
}
//設(shè)置operation的類型,可以自行再繼承子類去實(shí)現(xiàn)
- (void)setOperationClass:(Class)operationClass {
_operationClass = operationClass ?: [SDWebImageDownloaderOperation class];
}
//暫停下載
- (void)setSuspended:(BOOL)suspended {
[self.downloadQueue setSuspended:suspended];
}
//取消所有下載
- (void)cancelAllDownloads {
[self.downloadQueue cancelAllOperations];
}
這一部分唯一提一下的是setOperationClass:
方法半火,是為了可以自己實(shí)現(xiàn)NSOperation的子類去完成相關(guān)請(qǐng)求的設(shè)置越妈。
請(qǐng)求圖片方法
相關(guān)方法實(shí)現(xiàn)如下:
//請(qǐng)求圖片方法
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
__block SDWebImageDownloaderOperation *operation;
__weak __typeof(self)wself = self;
//處理新請(qǐng)求的封裝
[self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^{
NSTimeInterval timeoutInterval = wself.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
//為了防止?jié)撛诘闹貜?fù)cache
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
//設(shè)置是否處理cookies
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
request.HTTPShouldUsePipelining = YES;
//有header處理block,則調(diào)用block钮糖,返回headerfieleds
if (wself.headersFilter) {
request.allHTTPHeaderFields = wself.headersFilter(url, [wself.HTTPHeaders copy]);
}
else {
request.allHTTPHeaderFields = wself.HTTPHeaders;
}
//這邊的創(chuàng)建方式允許自己定義SDWebImageDownloaderOperation的子類進(jìn)行替換
operation = [[wself.operationClass alloc] initWithRequest:request
inSession:self.session
options:options
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
SDWebImageDownloader *sself = wself;
if (!sself) return;
__block NSArray *callbacksForURL;
//這里用barrierQueue可以確保數(shù)據(jù)一致性
dispatch_sync(sself.barrierQueue, ^{
//獲取所有的同url的請(qǐng)求
callbacksForURL = [sself.URLCallbacks[url] copy];
});
//遍歷創(chuàng)建信息函數(shù)
for (NSDictionary *callbacks in callbacksForURL) {
dispatch_async(dispatch_get_main_queue(), ^{
//獲取progressblock并調(diào)用
SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey];
if (callback) callback(receivedSize, expectedSize);
});
}
}
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
SDWebImageDownloader *sself = wself;
if (!sself) return;
__block NSArray *callbacksForURL;
//等待新請(qǐng)求插入和獲取參數(shù)完結(jié)梅掠,再finish
dispatch_barrier_sync(sself.barrierQueue, ^{
callbacksForURL = [sself.URLCallbacks[url] copy];
//如果完結(jié)了,刪除所有url緩存
if (finished) {
[sself.URLCallbacks removeObjectForKey:url];
}
});
//調(diào)用completeblock
for (NSDictionary *callbacks in callbacksForURL) {
SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
if (callback) callback(image, data, error, finished);
}
}
cancelled:^{
SDWebImageDownloader *sself = wself;
if (!sself) return;
//刪除url
dispatch_barrier_async(sself.barrierQueue, ^{
[sself.URLCallbacks removeObjectForKey:url];
});
}];
//是否壓縮圖片
operation.shouldDecompressImages = wself.shouldDecompressImages;
//是否包含url的驗(yàn)證
if (wself.urlCredential) {
operation.credential = wself.urlCredential;
} else if (wself.username && wself.password) {
//有賬號(hào)和密碼則設(shè)置請(qǐng)求
operation.credential = [NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession];
}
//設(shè)置operation請(qǐng)求優(yōu)先級(jí)
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
}
//開始請(qǐng)求[operation start]
[wself.downloadQueue addOperation:operation];
//如果是先進(jìn)后出店归,鏈接依賴
if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
// Emulate LIFO execution order by systematically adding new operations as last operation's dependency
[wself.lastAddedOperation addDependency:operation];
wself.lastAddedOperation = operation;
}
}];
return operation;
}
//用來處理新請(qǐng)求的封裝函數(shù)
- (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageNoParamsBlock)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;
}
//添加progress的過程都會(huì)等待queue中的前面完成后進(jìn)行
dispatch_barrier_sync(self.barrierQueue, ^{
BOOL first = NO;
//如果url不存在在self.URLCallbacks中阎抒,說明該url第一次下載
if (!self.URLCallbacks[url]) {
self.URLCallbacks[url] = [NSMutableArray new];
first = YES;
}
// Handle single download of simultaneous download request for the same URL
//處理同時(shí)同樣的url請(qǐng)求情況
//將progressblock和completeblock存入dictionary,再添加到self.URLCallbacks[url]的array中消痛,這樣可以在運(yùn)行完成后一起運(yùn)行
NSMutableArray *callbacksForURL = self.URLCallbacks[url];
NSMutableDictionary *callbacks = [NSMutableDictionary new];
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
[callbacksForURL addObject:callbacks];
self.URLCallbacks[url] = callbacksForURL;
if (first) {
createCallback();
}
});
}
可以理解分為兩塊:
- 1.請(qǐng)求封裝參數(shù)
這一塊的主要目的是存儲(chǔ)請(qǐng)求的progressBlock(進(jìn)度回調(diào))和completeBlock(完成回調(diào))到self.URLCallbacks中且叁,并且對(duì)于同時(shí)多個(gè)請(qǐng)求同一個(gè)url圖片的情況進(jìn)行了處理。 - 2.新請(qǐng)求獲取圖片
當(dāng)url為第一次請(qǐng)求時(shí)候秩伞,構(gòu)建請(qǐng)求request與operation逞带,并開始運(yùn)行operation
NSURLSession相關(guān)回調(diào)
相關(guān)方法如下:
#pragma mark Helper methods
//獲取operationQueue中的包含此task的operation
- (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
//收到返回結(jié)果
- (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];
}
//收到返回?cái)?shù)據(jù)
- (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];
}
//是否緩存reponse
- (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
//task已經(jīng)完成
- (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];
}
//task請(qǐng)求驗(yàn)證方法
- (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];
}
實(shí)際上相關(guān)處理都已經(jīng)放到NSOperation進(jìn)行處理欺矫。
SDWebImageDownloaderOperation
SDWebImageDownloaderOperation主要包含以下內(nèi)容:
- 初始化相關(guān)信息
- 相關(guān)參數(shù)設(shè)置
- 開始請(qǐng)求與取消請(qǐng)求
- NSURLSession相關(guān)回調(diào)
初始化相關(guān)信息
SDWebImageDownloaderOperation.h的property聲明
@interface SDWebImageDownloaderOperation : NSOperation <SDWebImageOperation, NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
//operation的請(qǐng)求
@property (strong, nonatomic, readonly) NSURLRequest *request;
//operation的task
@property (strong, nonatomic, readonly) NSURLSessionTask *dataTask;
//是否壓縮圖片
@property (assign, nonatomic) BOOL shouldDecompressImages;
//當(dāng)收到`-connection:didReceiveAuthenticationChallenge:`的驗(yàn)證
@property (nonatomic, strong) NSURLCredential *credential;
//下載方式
@property (assign, nonatomic, readonly) SDWebImageDownloaderOptions options;
//文件大小
@property (assign, nonatomic) NSInteger expectedSize;
//operation的返回
@property (strong, nonatomic) NSURLResponse *response;
SDWebImageDownloaderOperation.m中的extension
@interface SDWebImageDownloaderOperation ()
@property (copy, nonatomic) SDWebImageDownloaderProgressBlock progressBlock;
@property (copy, nonatomic) SDWebImageDownloaderCompletedBlock completedBlock;
@property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock;
@property (assign, nonatomic, getter = isExecuting) BOOL executing;
@property (assign, nonatomic, getter = isFinished) BOOL finished;
@property (strong, nonatomic) NSMutableData *imageData;
// This is weak because it is injected by whoever manages this session. If this gets nil-ed out, we won't be able to run
// the task associated with this operation
//因?yàn)槭潜籨ownloader持有,所以是weak
@property (weak, nonatomic) NSURLSession *unownedSession;
// This is set if we're using not using an injected NSURLSession. We're responsible of invalidating this one
//如果不用設(shè)置的nsurlsession展氓,需要自己去吃油一個(gè)
@property (strong, nonatomic) NSURLSession *ownedSession;
//sessiontask
@property (strong, nonatomic, readwrite) NSURLSessionTask *dataTask;
//當(dāng)前線程
@property (strong, atomic) NSThread *thread;
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId;
#endif
@end
@implementation SDWebImageDownloaderOperation {
//圖片長(zhǎng),寬
size_t width, height;
//圖片方向
UIImageOrientation orientation;
//是否圖片是從cache讀出
BOOL responseFromCached;
}
初始化相關(guān)方法:
- (id)initWithRequest:(NSURLRequest *)request
inSession:(NSURLSession *)session
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock
cancelled:(SDWebImageNoParamsBlock)cancelBlock {
if ((self = [super init])) {
_request = [request copy];
_shouldDecompressImages = YES;
_options = options;
_progressBlock = [progressBlock copy];
_completedBlock = [completedBlock copy];
_cancelBlock = [cancelBlock copy];
_executing = NO;
_finished = NO;
_expectedSize = 0;
_unownedSession = session;
responseFromCached = YES; // Initially wrong until `- URLSession:dataTask:willCacheResponse:completionHandler: is called or not called
}
return self;
}
同樣穆趴,從初始化的參數(shù)可以看出,在SDWebImageDownloaderOperation中遇汞,通過NSURLRequest去生成一個(gè)NSURLSessionTask未妹,然后建立鏈接,獲取信息空入。
相關(guān)參數(shù)設(shè)置
相關(guān)實(shí)現(xiàn)如下:
//重寫set方法络它,并且設(shè)置kvo的觀察回調(diào)
- (void)setFinished:(BOOL)finished {
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
//重寫excute方法,并且設(shè)置kvo的觀察回調(diào)
- (void)setExecuting:(BOOL)executing {
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}
//是否允許并發(fā)
- (BOOL)isConcurrent {
return YES;
}
這里重寫的時(shí)候還注意到了原來相關(guān)屬性的KVO值执庐。
開始請(qǐng)求與取消請(qǐng)求
主要就是重寫NSOperation的start
和cancel
方法,其中cancel
方法還是SDWebImageOperation
的回調(diào)导梆。
相關(guān)方法如下:
//重新父類start開始方法
- (void)start {
//加鎖
@synchronized (self) {
//如果已經(jīng)取消轨淌,則取消
if (self.isCancelled) {
self.finished = YES;
//重置數(shù)據(jù)
[self reset];
return;
}
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
//在iOS4以上,會(huì)去運(yùn)行后臺(tái)方法看尼,等待完成后递鹉,取消相關(guān)下載
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;
//沒有unowndSession,則創(chuà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;
}
//創(chuàng)建NSURLSessionDataTask
self.dataTask = [session dataTaskWithRequest:self.request];
//代表是否在執(zhí)行
self.executing = YES;
//創(chuàng)建thread藏斩,目的是能夠在該方法開始后躏结,取消方法在開始方法結(jié)束進(jìn)行調(diào)用
self.thread = [NSThread currentThread];
}
//開始執(zhí)行task
[self.dataTask resume];
if (self.dataTask) {
//初始化的進(jìn)度block
if (self.progressBlock) {
self.progressBlock(0, NSURLResponseUnknownLength);
}
//發(fā)一個(gè)開始下載的notification
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
});
}
else {
//不存在request就直接返回下載失敗
if (self.completedBlock) {
self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES);
}
}
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
//直接停止后臺(tái)下載動(dòng)作
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的deelgate
- (void)cancel {
@synchronized (self) {
//有self.thread說明該operation已經(jīng)開始下載了,需要把cancel方法放到下載的同一個(gè)線程狰域,并且等待下載完成后cancel
if (self.thread) {
[self performSelector:@selector(cancelInternalAndStop) onThread:self.thread withObject:nil waitUntilDone:NO];
}
else {
[self cancelInternal];
}
}
}
//線程cancel方法
- (void)cancelInternalAndStop {
if (self.isFinished) return;
[self cancelInternal];
}
//停止下載
- (void)cancelInternal {
if (self.isFinished) return;
[super cancel];
if (self.cancelBlock) self.cancelBlock();
//數(shù)據(jù)請(qǐng)求取消
if (self.dataTask) {
[self.dataTask cancel];
//發(fā)送stop notification
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.
//停止請(qǐng)求后媳拴,需要去設(shè)置相關(guān)方法
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 {
self.cancelBlock = nil;
self.completedBlock = nil;
self.progressBlock = nil;
self.dataTask = nil;
self.imageData = nil;
self.thread = nil;
if (self.ownedSession) {
[self.ownedSession invalidateAndCancel];
self.ownedSession = nil;
}
}
這里比較有趣的是self.thread
的妙用,如果存在self.thread
說明operation已經(jīng)start兆览,那么cancel已經(jīng)無效屈溉,必須等待請(qǐng)求完成后,才能去cancel它抬探,防止出現(xiàn)問題子巾。這邊用self.thread
就可以達(dá)成這樣的目的。
NSURLSession相關(guān)回調(diào)
相關(guān)方法如下:
#pragma mark NSURLSessionDataDelegate
//收到請(qǐng)求回復(fù)
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
//'304 Not Modified' is an exceptional one
//除了304以外<400的工程情況
if (![response respondsToSelector:@selector(statusCode)] || ([((NSHTTPURLResponse *)response) statusCode] < 400 && [((NSHTTPURLResponse *)response) statusCode] != 304)) {
NSInteger expected = response.expectedContentLength > 0 ? (NSInteger)response.expectedContentLength : 0;
//設(shè)置接受數(shù)據(jù)大小
self.expectedSize = expected;
//初始化progress過程
if (self.progressBlock) {
self.progressBlock(0, expected);
}
//創(chuàng)建能夠接受的圖片大小數(shù)據(jù)
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然后返回cache
if (code == 304) {
[self cancelInternal];
} else {
//其他情況停止請(qǐng)求
[self.dataTask cancel];
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
});
//完成block线梗,返回錯(cuò)誤
if (self.completedBlock) {
self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:[((NSHTTPURLResponse *)response) statusCode] userInfo:nil], YES);
}
//結(jié)束
[self done];
}
//回調(diào)response允許調(diào)用
if (completionHandler) {
completionHandler(NSURLSessionResponseAllow);
}
}
//收到請(qǐng)求數(shù)據(jù),分進(jìn)度
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
//數(shù)據(jù)添加到imageData緩存
[self.imageData appendData:data];
//如果需要顯示進(jìn)度
if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0 && self.completedBlock) {
// 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
//獲得下載的圖片大小
const NSInteger totalSize = self.imageData.length;
// Update the data source, we must pass ALL the data, not just the new bytes
//更新數(shù)據(jù)怠益,需要把所有數(shù)據(jù)一同更新
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)self.imageData, NULL);
//為0說明是第一段數(shù)據(jù)
if (width + height == 0) {
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);
//獲得圖片旋轉(zhuǎn)方向
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.
//當(dāng)我們繪制Core Graphic仪搔,我們會(huì)失去圖片方向信息
//著意味著用initWithCGIImage將會(huì)有的時(shí)候并不正確,(不像在didCompleteWithError里用initWithData),所以保存信息
orientation = [[self class] orientationFromPropertyValue:(orientationValue == -1 ? 1 : orientationValue)];
}
}
//不是第一段數(shù)據(jù)蜻牢,并且沒有下載完畢
if (width + height > 0 && totalSize < self.expectedSize) {
// Create the image
//創(chuàng)建圖片來源
CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
#ifdef TARGET_OS_IPHONE
// Workaround for iOS anamorphic image
//處理iOS失真圖片僻造,這邊的處理方式有寫看不大懂
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
//如果有了圖片數(shù)據(jù)
if (partialImageRef) {
//獲取圖片
UIImage *image = [UIImage imageWithCGImage:partialImageRef scale:1 orientation:orientation];
//獲得key
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
//獲得適合屏幕的圖片
UIImage *scaledImage = [self scaledImageForKey:key image:image];
if (self.shouldDecompressImages) {
//壓縮圖片
image = [UIImage decodedImageWithImage:scaledImage];
}
else {
image = scaledImage;
}
CGImageRelease(partialImageRef);
//返回完成結(jié)果
dispatch_main_sync_safe(^{
if (self.completedBlock) {
self.completedBlock(image, nil, nil, NO);
}
});
}
}
CFRelease(imageSource);
}
//顯示階段
if (self.progressBlock) {
self.progressBlock(self.imageData.length, self.expectedSize);
}
}
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
//說明結(jié)果沒有從cache讀取
responseFromCached = NO; // If this method is called, it means the response wasn't read from cache
NSCachedURLResponse *cachedResponse = proposedResponse;
//如果是放棄cache的模式
if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) {
// Prevents caching of responses
cachedResponse = nil;
}
//回調(diào)結(jié)果cachedResponse
if (completionHandler) {
completionHandler(cachedResponse);
}
}
#pragma mark NSURLSessionTaskDelegate
//完成請(qǐng)求
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
@synchronized(self) {
//完成后的數(shù)據(jù)設(shè)置
self.thread = nil;
self.dataTask = nil;
//停止notification
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
if (!error) {
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:self];
}
});
}
//如果有error憋他,將error返回
if (error) {
if (self.completedBlock) {
self.completedBlock(nil, nil, error, YES);
}
} else {
//如果存在完成block
SDWebImageDownloaderCompletedBlock completionBlock = self.completedBlock;
if (completionBlock) {
/**
* See #1608 and #1623 - apparently, there is a race condition on `NSURLCache` that causes a crash
* Limited the calls to `cachedResponseForRequest:` only for cases where we should ignore the cached response
* and images for which responseFromCached is YES (only the ones that cannot be cached).
* Note: responseFromCached is set to NO inside `willCacheResponse:`. This method doesn't get called for large images or images behind authentication
*/
//查看#1608和#1623的pull request。顯然髓削,這里會(huì)有一個(gè)罕見的NSURLCache的crash
//限制了調(diào)用cachedResponseForRequest竹挡,只有當(dāng)responseFromCached為yes的時(shí)候,我們應(yīng)該去忽略緩存的reponse和圖片
//記錄:當(dāng)responseFromCached在`willCacheResponse:`設(shè)置為no立膛。這個(gè)方法不會(huì)在大型圖片和驗(yàn)證圖片的時(shí)候到靠用
if (self.options & SDWebImageDownloaderIgnoreCachedResponse && responseFromCached && [[NSURLCache sharedURLCache] cachedResponseForRequest:self.request]) {
completionBlock(nil, nil, nil, YES);
} else if (self.imageData) {
//初始化圖片
UIImage *image = [UIImage sd_imageWithData:self.imageData];
//緩存的url的key
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
//適應(yīng)屏幕設(shè)置
image = [self scaledImageForKey:key image:image];
// Do not force decoding animated GIFs
//如果不是GIF
if (!image.images) {
if (self.shouldDecompressImages) {
//壓縮圖片
image = [UIImage decodedImageWithImage:image];
}
}
//圖片大小為0揪罕,則報(bào)錯(cuò)
if (CGSizeEqualToSize(image.size, CGSizeZero)) {
completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}], YES);
}
else {
//完成整個(gè)圖片處理
completionBlock(image, self.imageData, nil, YES);
}
} else {
//圖片為空
completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}], YES);
}
}
}
self.completionBlock = nil;
[self done];
}
//處理請(qǐng)求特殊權(quán)限驗(yàn)證
- (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ù)
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
//不需要信任,則走默認(rèn)處理
if (!(self.options & SDWebImageDownloaderAllowInvalidSSLCertificates)) {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
} else {
//設(shè)置站點(diǎn)為信任
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
disposition = NSURLSessionAuthChallengeUseCredential;
}
} else {
//有一些錯(cuò)誤
if ([challenge previousFailureCount] == 0) {
if (self.credential) {
//使用credential
credential = self.credential;
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
}
//回調(diào)驗(yàn)證信息
if (completionHandler) {
completionHandler(disposition, credential);
}
}
#pragma mark Helper methods
//返回圖片方向
+ (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;
}
}
- (UIImage *)scaledImageForKey:(NSString *)key image:(UIImage *)image {
return SDScaledImageForKey(key, image);
}
//是否應(yīng)該在后臺(tái)運(yùn)行
- (BOOL)shouldContinueWhenAppEntersBackground {
return self.options & SDWebImageDownloaderContinueInBackground;
}
這邊的代碼比較多宝泵,比較有意思的地方主要在于以下兩點(diǎn):
- 1.圖片的處理
在下載過程當(dāng)中好啰,就對(duì)已經(jīng)接受數(shù)據(jù)的信息并且轉(zhuǎn)換成了模糊化的圖片,并且回調(diào)了self.completedBlock(image, nil, nil, NO);
儿奶,說明可以做到圖片逐漸清晰的這種效果框往。 - 2.驗(yàn)證信息的方式
didReceiveChallenge
方法代表在請(qǐng)求時(shí)候,遇到一些驗(yàn)證闯捎。
總結(jié)
總的來說椰弊,Downloader有以下優(yōu)點(diǎn):
- 1.設(shè)計(jì)上支持并發(fā)的下載同一個(gè)url的圖片,并且有正確的回調(diào)
- 2.通過barrier去確保獲取數(shù)據(jù)不會(huì)與設(shè)置數(shù)據(jù)有沖突
- 3.妙用thread去達(dá)到nsoperation無法取消但可以等待完成后取消的結(jié)果
- 4.神奇的圖片處理瓤鼻,一開始獲取圖片的高秉版、寬、方向茬祷,然后通過神奇的方法清焕,進(jìn)行圖片展示,這個(gè)方法到現(xiàn)在為止我還是沒看懂它是如何處理的祭犯,如果有懂的人麻煩指點(diǎn)一下
- 5.NSURLSession的使用(在這之前我只用過NSURLConnection秸妥,對(duì)ios7出來的NSURLSession確實(shí)了解不多)
- 6.對(duì)于驗(yàn)證請(qǐng)求的處理
更新:關(guān)于圖片處理這塊,在utils的解碼中沃粗,有詳細(xì)一些的標(biāo)注筛峭,可以稍微用來理解這里的圖片處理。
參考資料
1.SDWebImage源碼淺析
2.Apple Guide:URL Session Programming Guide