SDWebImage源碼解析(三)

在前面的SDWebImage源碼解析(一)SDWebImage源碼解析(二)中,解析了開源異步圖片下載庫(kù)SDWebImage的緩存部分。接下來本篇文章將對(duì)SDWebImage的下載器部分進(jìn)行解析。

SDWebImage的異步下載器SDWebImageDownload利用它的單例對(duì)象sharedDownloader庆揩,可以很好的對(duì)圖片的下載過程進(jìn)行配置虏辫。sharedDownloader可以配置的部分:

  1. 下載選項(xiàng)
  2. HTTP頭部
  3. 壓縮、下載順序娄昆、最大并發(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ā)下載檐嚣。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市啰扛,隨后出現(xiàn)的幾起案子嚎京,更是在濱河造成了極大的恐慌,老刑警劉巖隐解,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鞍帝,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡煞茫,警方通過查閱死者的電腦和手機(jī)帕涌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來续徽,“玉大人蚓曼,你說我怎么就攤上這事∏张ぃ” “怎么了纫版?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)土全。 經(jīng)常有香客問我捎琐,道長(zhǎng),這世上最難降的妖魔是什么裹匙? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任瑞凑,我火速辦了婚禮,結(jié)果婚禮上概页,老公的妹妹穿的比我還像新娘籽御。我一直安慰自己,他們只是感情好惰匙,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布技掏。 她就那樣靜靜地躺著,像睡著了一般项鬼。 火紅的嫁衣襯著肌膚如雪哑梳。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天绘盟,我揣著相機(jī)與錄音鸠真,去河邊找鬼悯仙。 笑死,一個(gè)胖子當(dāng)著我的面吹牛吠卷,可吹牛的內(nèi)容都是我干的锡垄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼祭隔,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼货岭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起疾渴,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤千贯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后程奠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體丈牢,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年瞄沙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了己沛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡距境,死狀恐怖申尼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情垫桂,我是刑警寧澤师幕,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站诬滩,受9級(jí)特大地震影響霹粥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜疼鸟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一后控、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧空镜,春花似錦浩淘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至洼怔,卻和暖如春署惯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背镣隶。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工极谊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留什荣,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓怀酷,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親嗜闻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蜕依,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容