SD源碼中下載只涉及到兩個(gè)類析桥,一個(gè)是SDWebImageDownloader司草,另外一個(gè)是SDWebImageDownloaderOperation艰垂。由文件命名不難看出,SD采用的是NSOperation異步下載的方式埋虹。下面就說(shuō)說(shuō)它的幾個(gè)細(xì)節(jié)處理猜憎。
- 如何實(shí)現(xiàn)一個(gè)下載manager?
- 如何處理一個(gè)異步下載搔课?
- 一個(gè)下載任務(wù)具體實(shí)現(xiàn)胰柑?
- 其他細(xì)節(jié)處理
1. 如何實(shí)現(xiàn)一個(gè)下載manager?
通常設(shè)計(jì)一個(gè)Manager就是對(duì)外提供一個(gè)統(tǒng)一的接口爬泥,Manager應(yīng)該具備的功能主要有就是對(duì)被操作對(duì)象進(jìn)行管理(增加柬讨,刪除,修改)和處理操作(具體就是被操作對(duì)象的功能方法)并且維護(hù)任務(wù)狀態(tài)急灭。要實(shí)現(xiàn)一個(gè)Manager姐浮,常規(guī)做法就是維護(hù)一個(gè)對(duì)象容器,并對(duì)該容器進(jìn)行相關(guān)操作葬馋。在此處卖鲤,SDWebImageDownloader也是這樣來(lái)實(shí)現(xiàn)的。其具體做法就是內(nèi)部維護(hù)一個(gè)NSOperationqueue畴嘶,來(lái)處理外部下載任務(wù)蛋逾。同時(shí),它也維護(hù)一個(gè)下載任務(wù)的容器窗悯,用來(lái)維護(hù)對(duì)應(yīng)任務(wù)的一個(gè)狀態(tài)区匣。它提供了實(shí)以下兩個(gè)方法,如下:
添加并執(zhí)行一個(gè)下載任務(wù)
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock;
設(shè)置是否掛起一個(gè)任務(wù)
- (void)setSuspended:(BOOL)suspended;
除了以上方法蒋院,SDWebImageDownloader提供了一些配置類操作亏钩,比如設(shè)置最大并發(fā)數(shù),設(shè)置Http相關(guān)配置等欺旧。
2. 如何處理一個(gè)異步下載姑丑?
SDWebImageDownloader處理一個(gè)下載任務(wù)很簡(jiǎn)單,就只是把下載任務(wù)添加到并發(fā)隊(duì)列辞友,同時(shí)維護(hù)該下載任務(wù)的一個(gè)狀態(tài)即可栅哀。
1. 維護(hù)任務(wù)狀態(tài)
一個(gè)下載任務(wù)通常具有以下幾個(gè)狀態(tài):1.下載開始,2.下載中称龙,3.下載結(jié)束留拾,4.下載取消。SDWebImageDownloader對(duì)外通過(guò)提供block回調(diào)來(lái)處理下載結(jié)束鲫尊,下載中的一個(gè)狀態(tài)痴柔。具體有以下幾個(gè)block:
typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize);
typedef void(^SDWebImageDownloaderCompletedBlock)(UIImage *image, NSData *data, NSError *error, BOOL finished);
typedef void(^SDWebImageNoParamsBlock)();
SDWebImageDownloader在外部調(diào)用downloadImageWithURL時(shí),通過(guò)被請(qǐng)求URL來(lái)創(chuàng)建一個(gè)字典用來(lái)維護(hù)該任務(wù)的一個(gè)狀態(tài)處理block疫向,在這里竞帽,SDWebImageDownloader對(duì)相同URL請(qǐng)求進(jìn)行了過(guò)濾扛施,即多次請(qǐng)求相同URL任務(wù)只會(huì)被處理一次。
dispatch_barrier_sync(self.barrierQueue, ^{
BOOL first = NO;
if (!self.URLCallbacks[url]) {
self.URLCallbacks[url] = [NSMutableArray new];
first = YES;
}
// Handle single download of simultaneous download request for the same URL
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();
}
});
block處理下載中狀態(tài)
SDWebImageDownloader *sself = wself;
if (!sself) return;
__block NSArray *callbacksForURL;
dispatch_sync(sself.barrierQueue, ^{
//獲取當(dāng)前狀態(tài)維護(hù)隊(duì)列
callbacksForURL = [sself.URLCallbacks[url] copy];
});
for (NSDictionary *callbacks in callbacksForURL) { dispatch_async(dispatch_get_main_queue(), ^{
//獲取Url對(duì)應(yīng)的下載處理block
SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey];
if (callback) callback(receivedSize, expectedSize);
});
}
block處理下載完成狀態(tài)
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) {
//獲取下載完成處理block
SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
if (callback) callback(image, data, error, finished);
}
block處理下載取消狀態(tài)
SDWebImageDownloader *sself = wself;
if (!sself) return;
dispatch_barrier_async(sself.barrierQueue, ^{
[sself.URLCallbacks removeObjectForKey:url];
});
2. 創(chuàng)建任務(wù)并執(zhí)行
由于SDWebImageDownloader是通過(guò)NSOperation來(lái)處理并發(fā)任務(wù)屹篓,所以實(shí)現(xiàn)起來(lái)比較簡(jiǎn)單。創(chuàng)建一個(gè)NSOperation匙奴,并把它添加到Queue隊(duì)列即可堆巧。在此處,SDWebImageDownloader創(chuàng)建了一個(gè)并發(fā)隊(duì)列泼菌,默認(rèn)的最大并發(fā)數(shù)6谍肤。
SDWebImageDownloaderOperation是下載任務(wù)的具體實(shí)現(xiàn)類,它繼承NSOperation哗伯,且該類通過(guò)Url請(qǐng)求來(lái)進(jìn)行初始化荒揣。在處理隊(duì)列中任務(wù)時(shí),提供了兩種處理方式焊刹,一種是FIFO系任,一種是LIFO。如果采用的是LIFO方式處理虐块,則在把新任務(wù)加到隊(duì)列的同時(shí)俩滥,需要更新一下最后入隊(duì)列的任務(wù),并且該任務(wù)依賴當(dāng)前新加入的任務(wù)贺奠。具體實(shí)現(xiàn)如下:
[wself.downloadQueue addOperation:operation];
if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
[wself.lastAddedOperation addDependency:operation];
wself.lastAddedOperation = operation;
}
3. 一個(gè)下載任務(wù)具體實(shí)現(xiàn)
SDWebImageDownloaderOperation是下載任務(wù)的具體實(shí)現(xiàn)類霜旧。該類維護(hù)了自生的一個(gè)狀態(tài),一個(gè)NSURLConnection儡率,一個(gè)下載線程挂据,還有具體的下載后數(shù)據(jù)。在NSOperation的start方法中儿普。創(chuàng)建一個(gè)NSURLConnection并且開始這個(gè)connection崎逃,同時(shí),開啟一個(gè)后臺(tái)線程用于下載數(shù)據(jù)箕肃。
- (void)start {
//初始化當(dāng)前狀態(tài)
@synchronized (self) {
if (self.isCancelled) {
self.finished = YES;
[self reset];
return;
}
...
...
self.executing = YES;
self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];
self.thread = [NSThread currentThread];
}
//執(zhí)行connection
[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];
});
//開啟線程
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);
}
}
由于SDWebImageDownloaderOperation是通過(guò)NSURLConnection來(lái)下載圖片婚脱。所以,具體的下載動(dòng)作都由NSURLConnection的delegate來(lái)處理勺像。
收到http的response時(shí)的回調(diào)障贸,在該回調(diào)里面,首先根據(jù)response返回碼來(lái)判斷請(qǐng)求是否成功吟宦。請(qǐng)求成功篮洁,則通過(guò)respone中文件大小來(lái)創(chuàng)建一個(gè)下載數(shù)據(jù)緩存區(qū)。請(qǐng)求失敗殃姓,則主動(dòng)取消改請(qǐng)求袁波,并且停止后臺(tái)下載線程瓦阐,復(fù)位當(dāng)前任務(wù)狀態(tài)。
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
//'304 Not Modified' is an exceptional one
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];
//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.
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];
}
}
收到具體下載數(shù)據(jù)時(shí)候的回調(diào)篷牌,在該回調(diào)里面睡蟋,首先對(duì)下載到的數(shù)據(jù)進(jìn)行緩存,然后根據(jù)當(dāng)前緩存到的數(shù)據(jù)枷颊,來(lái)獲取到圖片的長(zhǎng)和寬戳杀,同時(shí),根據(jù)請(qǐng)求下載的數(shù)據(jù)大小來(lái)做判斷夭苗,如果符合信卡,則生成下載的圖片并回調(diào)給上層。
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.imageData appendData:data];
if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0 && self.completedBlock) {
// 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
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)self.imageData, NULL);
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);
// 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 connectionDidFinishLoading.) So save it here and pass it on later.
orientation = [[self class] orientationFromPropertyValue:(orientationValue == -1 ? 1 : orientationValue)];
}
}
if (width + height > 0 && totalSize < self.expectedSize) {
// Create the image
CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
#ifdef TARGET_OS_IPHONE
// 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) {
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);
}
}
請(qǐng)求失敗時(shí)的回調(diào)處理题造,終止后臺(tái)下載線程傍菇,并復(fù)位當(dāng)前請(qǐng)求。
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
@synchronized(self) {
CFRunLoopStop(CFRunLoopGetCurrent());
self.thread = nil;
self.connection = nil;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
});
}
if (self.completedBlock) {
self.completedBlock(nil, nil, error, YES);
}
self.completionBlock = nil;
[self done];
}
請(qǐng)求加載完成時(shí)的回調(diào)處理界赔,在該回調(diào)處理中丢习,首先就是停止了后臺(tái)下載線程,復(fù)位當(dāng)前請(qǐng)求仔蝌。然后判斷該請(qǐng)求是否已經(jīng)被緩存了泛领,如果已經(jīng)緩存,則取出緩存數(shù)據(jù)進(jìn)行解碼操作敛惊,生成相應(yīng)圖片并回調(diào)給上層
- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection {
SDWebImageDownloaderCompletedBlock completionBlock = self.completedBlock;
@synchronized(self) {
CFRunLoopStop(CFRunLoopGetCurrent());
self.thread = nil;
self.connection = nil;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:self];
});
}
if (![[NSURLCache sharedURLCache] cachedResponseForRequest:_request]) {
responseFromCached = NO;
}
if (completionBlock) {
if (self.options & SDWebImageDownloaderIgnoreCachedResponse && responseFromCached) {
completionBlock(nil, nil, nil, YES);
} else if (self.imageData) {
UIImage *image = [UIImage sd_imageWithData:self.imageData];
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
image = [self scaledImageForKey:key image:image];
// Do not force decoding animated GIFs
if (!image.images) {
if (self.shouldDecompressImages) {
image = [UIImage decodedImageWithImage:image];
}
}
if (CGSizeEqualToSize(image.size, CGSizeZero)) {
completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}], YES);
}
else {
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];
}
除了以上幾個(gè)比較重要的回調(diào)處理渊鞋。SDWebImageDownloaderOperation還處理了http鑒權(quán),加載等相關(guān)回調(diào)瞧挤。
4. 其他細(xì)節(jié)處理
1. 通知上層應(yīng)用
針對(duì)具體的下載事件锡宋,需要及時(shí)通知上層,SDWebImageDownloaderOperation提供以下幾個(gè)通知來(lái)做到上層同步特恬。
NSString *const SDWebImageDownloadStartNotification = @"SDWebImageDownloadStartNotification";
NSString *const SDWebImageDownloadReceiveResponseNotification = @"SDWebImageDownloadReceiveResponseNotification";
NSString *const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNotification";
NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinishNotification";
2. 后臺(tái)線程的處理
針對(duì)系統(tǒng)后臺(tái)運(yù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;
}
}];
}
針對(duì)后臺(tái)下載線程的處理
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_5_1) {
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false);
}
else {
CFRunLoopRun();
}
總結(jié)
- 掌握Manager與operatior的基本實(shí)現(xiàn)方式
- 加深NSOperation执俩,NSURLConnection的應(yīng)用