在前面的SDWebImage源碼解析(一)和SDWebImage源碼解析(二)中,解析了開源異步圖片下載庫(kù)SDWebImage的緩存部分。接下來本篇文章將對(duì)SDWebImage的下載器部分進(jìn)行解析。
SDWebImage的異步下載器SDWebImageDownload利用它的單例對(duì)象sharedDownloader庆揩,可以很好的對(duì)圖片的下載過程進(jìn)行配置虏辫。sharedDownloader可以配置的部分:
- 下載選項(xiàng)
- HTTP頭部
- 壓縮、下載順序娄昆、最大并發(fā)數(shù)萌焰、下載超時(shí)等
下載選項(xiàng)
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
SDWebImageDownloaderLowPriority = 1 << 0,
SDWebImageDownloaderProgressiveDownload = 1 << 1,
/**
* 默認(rèn),request不使用NSURLCache撼玄。如果設(shè)置該選項(xiàng),則以默認(rèn)的緩存策略來使用NSURLCache
*/
SDWebImageDownloaderUseNSURLCache = 1 << 2,
/**
* 如果從NSURLCache緩存中讀取圖片磕蒲,則使用nil傳遞給imageData參數(shù)來調(diào)用完成block
*/
SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
/**
* 在iOS 4+系統(tǒng)中留潦,允許程序進(jìn)入后臺(tái)繼續(xù)下載圖片。該操作通過向系統(tǒng)申請(qǐng)額外的時(shí)間來完成后臺(tái)下載辣往。如果后臺(tái)任務(wù)超時(shí)兔院,則此操作會(huì)被取消。
*/
SDWebImageDownloaderContinueInBackground = 1 << 4,
/**
* 通過設(shè)置NSMutableURLRequest.HTTPShouldHandleCookies = YES來處理存儲(chǔ)在NSHTTPCookieStore中的cookie
*/
SDWebImageDownloaderHandleCookies = 1 << 5,
/**
* 設(shè)置允許非信任的SSL證書站削。主要用于測(cè)試目的
*/
SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,
/**
* 將圖像下載放到高優(yōu)先級(jí)隊(duì)列中
*/
SDWebImageDownloaderHighPriority = 1 << 7,
};
可以看出坊萝,這些選項(xiàng)主要涉及到下載的優(yōu)先級(jí)、緩存、后臺(tái)任務(wù)執(zhí)行、cookie處理以及認(rèn)證幾個(gè)方面狮崩。
HTTP頭部
@property (strong, nonatomic) NSMutableDictionary *HTTPHeaders;
#ifdef SD_WEBP
_HTTPHeaders = [@{@"Accept": @"image/webp,image/*;q=0.8"} mutableCopy];
#else
_HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy];
#endif
- (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è)置request.allHTTPHeaderFields來指定HTTP頭部坦敌,HTTP頭部中包含可接受圖片類型信息训柴,使用者也可以自己添加或者刪除HTTP頭部信息仗嗦。
線程安全
使用自定義的并行調(diào)度隊(duì)列barrierQueue處理所有下載操作的網(wǎng)絡(luò)響應(yīng)序列化任務(wù)铲咨。
// This queue is used to serialize the handling of the network responses of all the download operation in a single queue
@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;
為了保證線程安全摇天,所有增改回調(diào)集合URLCallbacks的操作使用dispatch_barrier_sync放入隊(duì)列barrierQueue中济赎,而查詢URLCallbakcs的操作只需使用dispatch_sync放入隊(duì)列barrierQueue中。
//查詢URLCallbacks
dispatch_sync(sself.barrierQueue, ^{
callbacksForURL = [sself.URLCallbacks[url] copy];
});
//增改URLCallbacks
dispatch_barrier_sync(self.barrierQueue, ^{
BOOL first = NO;
if (!self.URLCallbacks[url]) {
self.URLCallbacks[url] = [NSMutableArray new];
first = YES;
}
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();
}
});
回調(diào)
每一個(gè)圖片的下載都會(huì)對(duì)應(yīng)一些回調(diào)操作畅姊,如下載進(jìn)度回調(diào)倾鲫,下載完成回調(diào)等供屉,這些回調(diào)操作是以block形式來呈現(xiàn)的撵割,如下所示:
//下載進(jìn)度
typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize);
//下載完成
typedef void(^SDWebImageDownloaderCompletedBlock)(UIImage *image, NSData *data, NSError *error, BOOL finished);
//headers過濾
typedef NSDictionary *(^SDWebImageDownloaderHeadersFilterBlock)(NSURL *url, NSDictionary *headers);
圖片下載的這些回調(diào)信息存儲(chǔ)在SDWebImageDownloader類的URLCallbacks屬性中纵搁,該屬性是一個(gè)字典,key是圖片的URL地址,value則是一個(gè)數(shù)組斤寇,包含每個(gè)圖片的多組回調(diào)信息碎税。
下載器
整個(gè)下載器對(duì)于下載請(qǐng)求的管理都是放在downloadImageWithURL:options:progress:completed:方法里面來處理的。
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
// block中要修改的變量需要__block修飾
__block SDWebImageDownloaderOperation *operation;
// 防止retain cycle
__weak __typeof(self)wself = self;
// 調(diào)用另一方法將,并在創(chuàng)建回調(diào)的block中創(chuàng)建新的操作医男,配置之后將其放入downloadQueue操作隊(duì)列中踱启。
[self addProgressCallback:progressBlock andCompletedBlock:completedBlock forURL:url createCallback:^{
NSTimeInterval timeoutInterval = wself.downloadTimeout;
if (timeoutInterval == 0.0) {
timeoutInterval = 15.0;
}
// 創(chuàng)建請(qǐng)求對(duì)象,并根據(jù)options參數(shù)設(shè)置其屬性
// 為了避免潛在的重復(fù)緩存(NSURLCache+SDImageCache),如果沒有明確告知脓恕,則禁用圖片請(qǐng)求的緩存操作圆兵。
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
request.HTTPShouldUsePipelining = YES;
if (wself.headersFilter) {
request.allHTTPHeaderFields = wself.headersFilter(url, [wself.HTTPHeaders copy]);
}
else {
request.allHTTPHeaderFields = wself.HTTPHeaders;
}
// 創(chuàng)建SDWebImageDownloaderOperation操作對(duì)象茫陆,并進(jìn)行配置
operation = [[wself.operationClass alloc] initWithRequest:request
options:options
// 進(jìn)度回調(diào)調(diào)用
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
SDWebImageDownloader *sself = wself;
if (!sself) return;
__block NSArray *callbacksForURL;
dispatch_sync(sself.barrierQueue, ^{
callbacksForURL = [sself.URLCallbacks[url] copy];
});
for (NSDictionary *callbacks in callbacksForURL) {
dispatch_async(dispatch_get_main_queue(), ^{
SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey];
if (callback) callback(receivedSize, expectedSize);
});
}
}
// 完成回調(diào)調(diào)用
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
SDWebImageDownloader *sself = wself;
if (!sself) return;
__block NSArray *callbacksForURL;
dispatch_barrier_sync(sself.barrierQueue, ^{
callbacksForURL = [sself.URLCallbacks[url] copy];
if (finished) {
[sself.URLCallbacks removeObjectForKey:url];
}
});
for (NSDictionary *callbacks in callbacksForURL) {
SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
if (callback) callback(image, data, error, finished);
}
}
// 取消操作將該URL對(duì)應(yīng)的回調(diào)信息從URLCallbacks中刪除
cancelled:^{
SDWebImageDownloader *sself = wself;
if (!sself) return;
dispatch_barrier_async(sself.barrierQueue, ^{
[sself.URLCallbacks removeObjectForKey:url];
});
}];
operation.shouldDecompressImages = wself.shouldDecompressImages;
// operation的認(rèn)證配置
if (wself.username && wself.password) {
operation.credential = [NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession];
}
// operation的優(yōu)先級(jí)配置
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
}
// 將操作加入到操作隊(duì)列downloadQueue中
[wself.downloadQueue addOperation:operation];
// 如果是LIFO順序蝙泼,則將新的操作作為原隊(duì)列中的最后一個(gè)操作的依賴载荔,然后將新操作設(shè)置為最后一個(gè)操作呈础。
if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
[wself.lastAddedOperation addDependency:operation];
wself.lastAddedOperation = operation;
}
}];
return operation;
}
上面的方法調(diào)用了addProgressCallback:andCompletedBlock:forURL:createCallback:方法來將請(qǐng)求的信息存入下載器
- (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock andCompletedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageNoParamsBlock)createCallback {
// URL作為調(diào)用字典URLCallbacks的鍵值途凫,不能為空迅耘。如若為空的話只壳,會(huì)立即調(diào)用完成block么鹤,沒有圖像或者數(shù)據(jù)。
if (url == nil) {
if (completedBlock != nil) {
completedBlock(nil, nil, nil, NO);
}
return;
}
// 以dispatch_barrier_synv操作為保證同一時(shí)間只有一個(gè)線程能對(duì)URLCallbacks進(jìn)行操作程梦。
dispatch_barrier_sync(self.barrierQueue, ^{
BOOL first = NO;
if (!self.URLCallbacks[url]) {
self.URLCallbacks[url] = [NSMutableArray new];
first = YES;
}
// 處理同一URL的同步下載請(qǐng)求的單個(gè)下載
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();
}
});
}
下載操作
每個(gè)圖片的下載都是一個(gè)Operation操作错邦。我們?cè)谏厦娣治鲞^這個(gè)操作的創(chuàng)建及加入操作隊(duì)列的過程』昀梗現(xiàn)在我們來看看單個(gè)操作的具體實(shí)現(xiàn)腺逛。
SDWebImage定義了一個(gè)協(xié)議茁帽,即SDWebImageOperation作為圖片下載操作的基礎(chǔ)協(xié)議狰闪。它只聲明了一個(gè)cancel方法,用于取消操作雁社。協(xié)議的具體聲明如下:
@protocol SDWebImageOperation <NSObject>
- (void)cancel;
@end
SDWebImage自定義了一個(gè)Operation類浴井,即SDWebImageDownloaderOperation,它繼承自NSOperation霉撵,并采用了SDWebImageOperation協(xié)議磺浙。除了繼承而來的方法,該類只向外暴露了一個(gè)方法徒坡,即上面所用到的初始化方法initWithRequest:options:pregress:completed:cancelled:撕氧。
對(duì)于圖片的下載,SDWebImageDownloaderOperation完全依賴于URL加載系統(tǒng)中的NSURLConnection類(并未使用iOS7以后的NSURLSession類)喇完。我們先來分析一下SDWebImageDownloaderOperation類中對(duì)于圖片實(shí)際數(shù)據(jù)的下載處理伦泥,即NSURLConnection各代理方法的實(shí)現(xiàn)。
首先何暮,SDWebImageDownloaderOperation在Extention中采用了NSURLConnectionDataDelegate協(xié)議奄喂,并實(shí)現(xiàn)了協(xié)議的以下幾個(gè)方法:
connection:didReceiveResponse:
connection:didReceiveData:
connectionDidFinishLoading:
connection:didFailWithError:
connection:willCacheResponse:
connectionShouldUseCredentialStorage:
connection:willSendRequestForAuthenticationChallenge:
這些方法我們就不逐一分析了,就終點(diǎn)分析一下connection:didReceiveResponse:和connection:didReceiveData:兩個(gè)方法海洼。
connection:didReceiveResponse方法通過判斷NSURLResponse的實(shí)際類型和狀態(tài)碼跨新,對(duì)除304以外400以內(nèi)的狀態(tài)碼反應(yīng)。
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
//'304 Not Modified' 另外考慮
if (![response respondsToSelector:@selector(statusCode)] || ([((NSHTTPURLResponse *)response) statusCode] < 400 && [((NSHTTPURLResponse *)response) statusCode] != 304)) {
NSInteger expected = response.expectedContentLength > 0 ? (NSInteger)response.expectedContentLength : 0;
self.expectedSize = expected;
if (self.progressBlock) {
self.progressBlock(0, expected);
}
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];
// 當(dāng)服務(wù)器返回'304 Not Modified'的時(shí)候坏逢,意味著remote的圖像沒有改變域帐。
// 304的情況下我們僅需要取消operation并返回緩存中的圖像
if (code == 304) {
[self cancelInternal];
} else {
[self.connection cancel];
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
});
if (self.completedBlock) {
self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:[((NSHTTPURLResponse *)response) statusCode] userInfo:nil], YES);
}
CFRunLoopStop(CFRunLoopGetCurrent());
[self done];
}
}
connection:didReceiveData:方法的主要任務(wù)是接受數(shù)據(jù)。每次接收到數(shù)據(jù)時(shí)是整,都會(huì)用現(xiàn)有的數(shù)據(jù)創(chuàng)建一個(gè)CGImageSourceRef對(duì)象以作處理肖揣。在首次獲取到數(shù)據(jù)時(shí)(width+height==0)會(huì)從這些包含圖像信息的數(shù)據(jù)中取出圖像的長(zhǎng)、寬浮入、方向等信息以備使用龙优。而后在圖片下載完成之前,會(huì)使用CGImageSourceRef對(duì)象創(chuàng)建一個(gè)圖像對(duì)象事秀,經(jīng)過縮放彤断、解壓縮操作后生成一個(gè)UIImage對(duì)象供完成回調(diào)使用。當(dāng)然易迹,在這個(gè)方法中還需要處理的就是進(jìn)度信息宰衙。如果我們有設(shè)置進(jìn)度回調(diào)的話,就調(diào)用進(jìn)度回調(diào)以處理當(dāng)前圖片的下載進(jìn)度睹欲。
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.imageData appendData:data];
if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0 && self.completedBlock) {
// 以下的代碼來自于 http://www.cocoaintheshell.com/2011/05/progressive-images-download-imageio/
// 謝謝作者 @Nyx0uf
// 獲得已經(jīng)下載的總共字節(jié)數(shù)
const NSInteger totalSize = self.imageData.length;
// 更新數(shù)據(jù)源供炼,我們必須傳遞所有的數(shù)據(jù)一屋,而不只是新的字節(jié)
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)self.imageData, NULL);
// 首次獲取到數(shù)據(jù)時(shí),從這些數(shù)據(jù)中獲取圖片的長(zhǎng)袋哼、寬冀墨、方向?qū)傩灾? 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);
if (val) CFNumberGetValue(val, kCFNumberNSIntegerType, &orientationValue);
CFRelease(properties);
// 當(dāng)繪制到Core Graphics時(shí),我們會(huì)丟失方向信息先嬉,這意味著有時(shí)候由initWithCGIImage創(chuàng)建的
// 圖片的方向不對(duì)轧苫。(不像在connectionDidFinishLoading中用initWithData創(chuàng)建的圖片)
// 所以在這邊我們先保存這個(gè)信息并在后面使用
orientation = [[self class] orientationFromPropertyValue:(orientationValue == -1 ? 1 : orientationValue)];
}
}
// 圖片還未下載完成
if (width + height > 0 && totalSize < self.expectedSize) {
// 使用現(xiàn)有的數(shù)據(jù)創(chuàng)建圖片對(duì)象,如果數(shù)據(jù)中存有多張圖片疫蔓,則取第一張
CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
#ifdef TARGET_OS_IPHONE
// 適用于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
// 對(duì)圖片進(jìn)行縮放、解碼操作
if (partialImageRef) {
UIImage *image = [UIImage imageWithCGImage:partialImageRef scale:1 orientation:orientation];
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);
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);
}
}
我們前面說過SDWebImageDownloaderOperation類是繼承自NSOperation類衅胀。它沒有簡(jiǎn)單的實(shí)現(xiàn)main方法岔乔,而是采用更加靈活的start方法,以便自己管理下載的狀態(tài)滚躯。
在start方法中雏门,創(chuàng)建了我們下載所使用的NSURLConnection對(duì)象,開啟了圖片的下載掸掏,同時(shí)拋出一個(gè)下載開始的通知茁影。start方法的具體實(shí)現(xiàn)如下:
- (void)start {
@synchronized (self) {
//管理下載狀態(tài),如果已取消丧凤,則重置當(dāng)前下載并設(shè)置完成狀態(tài)YES
if (self.isCancelled) {
self.finished = YES;
[self reset];
return;
}
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
Class UIApplicationClass = NSClassFromString(@"UIApplication");
BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
// 如果設(shè)置了在后臺(tái)執(zhí)行募闲,則進(jìn)行后臺(tái)執(zhí)行
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
self.executing = YES;
self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];
self.thread = [NSThread currentThread];
}
[self.connection start];
if (self.connection) {
if (self.progressBlock) {
self.progressBlock(0, NSURLResponseUnknownLength);
}
// 在主線程拋出下載開始通知
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
});
// 確保我們的后臺(tái)線程啟動(dòng)runloop,這樣就能處理下載的數(shù)據(jù)愿待。
// Fail浩螺、Finish或者cancel的時(shí)候CFRunloopStop(CFRunloopGetCurrent())關(guān)閉runloop。如果不寫 // CFRunloopRun()仍侥,根本不會(huì)執(zhí)行NSURLConnection的代理方法的要出,因?yàn)樵摼€程沒開啟runloop,馬上就完了农渊。 // runloop相當(dāng)于子線程循環(huán)患蹂,可以靈活控制子線程的生命周期。
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_5_1) {
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false);
}
else {
CFRunLoopRun();
}
if (!self.isFinished) {
[self.connection cancel];
[self connection:self.connection didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorTimedOut userInfo:@{NSURLErrorFailingURLErrorKey : self.request.URL}]];
}
}
else {
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
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
}
當(dāng)然砸紊,在下載完成或者下載失敗后传于,需要停止當(dāng)前線程的runloop,清除連接批糟,并拋出下載停止的通知。如果下載成功看铆,則會(huì)處理完整的圖片數(shù)據(jù)徽鼎,對(duì)其進(jìn)行適當(dāng)?shù)目s放與解壓縮操作,以提供給完成回調(diào)使用。具體可參考-connectionDidFinishLoading:與-connection:didFailWithError:的實(shí)現(xiàn)否淤。
小結(jié)
下載的核心其實(shí)就是利用NSURLConnection對(duì)象來加載數(shù)據(jù)悄但。每個(gè)圖片的下載都由一個(gè)Operation操作來完成,并將這些操作放到一個(gè)操作隊(duì)列中石抡。這樣可以實(shí)現(xiàn)圖片的并發(fā)下載檐嚣。