iOS SDWebImage源码研究(三)
2016-04-11 11:37
507 查看
在前面的SDWebImage源码解析(一)和SDWebImage源码解析(二)中,解析了开源异步图片下载库SDWebImage的缓存部分。接下来本篇文章将对SDWebImage的下载器部分进行解析。
SDWebImage的异步下载器SDWebImageDownload利用它的单例对象sharedDownloader,可以很好的对图片的下载过程进行配置。sharedDownloader可以配置的部分:
下载选项
HTTP头部
压缩、下载顺序、最大并发数、下载超时等
可以看出,这些选项主要涉及到下载的优先级、缓存、后台任务执行、cookie处理以及认证几个方面。
通过设置request.allHTTPHeaderFields来指定HTTP头部,HTTP头部中包含可接受图片类型信息,使用者也可以自己添加或者删除HTTP头部信息。
为了保证线程安全,所有增改回调集合URLCallbacks的操作使用dispatch_barrier_sync放入队列barrierQueue中,而查询URLCallbakcs的操作只需使用dispatch_sync放入队列barrierQueue中。
每一个图片的下载都会对应一些回调操作,如下载进度回调,下载完成回调等,这些回调操作是以block形式来呈现的,如下所示:
图片下载的这些回调信息存储在SDWebImageDownloader类的URLCallbacks属性中,该属性是一个字典,key是图片的URL地址,value则是一个数组,包含每个图片的多组回调信息。
上面的方法调用了addProgressCallback:andCompletedBlock:forURL:createCallback:方法来将请求的信息存入下载器
SDWebImage定义了一个协议,即SDWebImageOperation作为图片下载操作的基础协议。它只声明了一个cancel方法,用于取消操作。协议的具体声明如下:
SDWebImage自定义了一个Operation类,即SDWebImageDownloaderOperation,它继承自NSOperation,并采用了SDWebImageOperation协议。除了继承而来的方法,该类只向外暴露了一个方法,即上面所用到的初始化方法initWithRequest:options:pregress:completed:cancelled:。
对于图片的下载,SDWebImageDownloaderOperation完全依赖于URL加载系统中的NSURLConnection类(并未使用iOS7以后的NSURLSession类)。我们先来分析一下SDWebImageDownloaderOperation类中对于图片实际数据的下载处理,即NSURLConnection各代理方法的实现。
首先,SDWebImageDownloaderOperation在Extention中采用了NSURLConnectionDataDelegate协议,并实现了协议的以下几个方法
这些方法我们就不逐一分析了,就终点分析一下connection:didReceiveResponse:和connection:didReceiveData:两个方法。
connection:didReceiveResponse方法通过判断NSURLResponse的实际类型和状态码,对除304以外400以内的状态码反应。
connection:didReceiveData:方法的主要任务是接受数据。每次接收到数据时,都会用现有的数据创建一个CGImageSourceRef对象以作处理。在首次获取到数据时(width+height==0)会从这些包含图像信息的数据中取出图像的长、宽、方向等信息以备使用。而后在图片下载完成之前,会使用CGImageSourceRef对象创建一个图像对象,经过缩放、解压缩操作后生成一个UIImage对象供完成回调使用。当然,在这个方法中还需要处理的就是进度信息。如果我们有设置进度回调的话,就调用进度回调以处理当前图片的下载进度。
我们前面说过SDWebImageDownloaderOperation类是继承自NSOperation类。它没有简单的实现main方法,而是采用更加灵活的start方法,以便自己管理下载的状态。
在start方法中,创建了我们下载所使用的NSURLConnection对象,开启了图片的下载,同时抛出一个下载开始的通知。start方法的具体实现如下:
当然,在下载完成或者下载失败后,需要停止当前线程的runloop,清除连接,并抛出下载停止的通知。如果下载成功,则会处理完整的图片数据,对其进行适当的缩放与解压缩操作,以提供给完成回调使用。具体可参考-connectionDidFinishLoading:与-connection:didFailWithError:的实现。
SDWebImage的异步下载器SDWebImageDownload利用它的单例对象sharedDownloader,可以很好的对图片的下载过程进行配置。sharedDownloader可以配置的部分:
下载选项
HTTP头部
压缩、下载顺序、最大并发数、下载超时等
下载选项
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) { SDWebImageDownloaderLowPriority = 1 << 0, SDWebImageDownloaderProgressiveDownload = 1 << 1, /** * 默认,request不使用NSURLCache。如果设置该选项,则以默认的缓存策略来使用NSURLCache */ SDWebImageDownloaderUseNSURLCache = 1 << 2, /** * 如果从NSURLCache缓存中读取图片,则使用nil传递给imageData参数来调用完成block */ SDWebImageDownloaderIgnoreCachedResponse = 1 << 3, /** * 在iOS 4+系统中,允许程序进入后台继续下载图片。该操作通过向系统申请额外的时间来完成后台下载。如果后台任务超时,则此操作会被取消。 */ SDWebImageDownloaderContinueInBackground = 1 << 4, /** * 通过设置NSMutableURLRequest.HTTPShouldHandleCookies = YES来处理存储在NSHTTPCookieStore中的cookie */ SDWebImageDownloaderHandleCookies = 1 << 5, /** * 设置允许非信任的SSL证书。主要用于测试目的 */ SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6, /** * 将图像下载放到高优先级队列中 */ SDWebImageDownloaderHighPriority = 1 << 7, };
可以看出,这些选项主要涉及到下载的优先级、缓存、后台任务执行、cookie处理以及认证几个方面。
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]; }
通过设置request.allHTTPHeaderFields来指定HTTP头部,HTTP头部中包含可接受图片类型信息,使用者也可以自己添加或者删除HTTP头部信息。
线程安全
使用自定义的并行调度队列barrierQueue处理所有下载操作的网络响应序列化任务。// 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;
为了保证线程安全,所有增改回调集合URLCallbacks的操作使用dispatch_barrier_sync放入队列barrierQueue中,而查询URLCallbakcs的操作只需使用dispatch_sync放入队列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(); } });
回调
每一个图片的下载都会对应一些回调操作,如下载进度回调,下载完成回调等,这些回调操作是以block形式来呈现的,如下所示://下载进度 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);
图片下载的这些回调信息存储在SDWebImageDownloader类的URLCallbacks属性中,该属性是一个字典,key是图片的URL地址,value则是一个数组,包含每个图片的多组回调信息。
下载器
整个下载器对于下载请求的管理都是放在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; // 调用另一方法将,并在创建回调的block中创建新的操作,配置之后将其放入downloadQueue操作队列中。 [self addProgressCallback:progressBlock andCompletedBlock:completedBlock forURL:url createCallback:^{ NSTimeInterval timeoutInterval = wself.downloadTimeout; if (timeoutInterval == 0.0) { timeoutInterval = 15.0; } // 创建请求对象,并根据options参数设置其属性 // 为了避免潜在的重复缓存(NSURLCache+SDImageCache),如果没有明确告知,则禁用图片请求的缓存操作。 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; } // 创建SDWebImageDownloaderOperation操作对象,并进行配置 operation = [[wself.operationClass alloc] initWithRequest:request options:options // 进度回调调用 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); }); } } // 完成回调调用 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对应的回调信息从URLCallbacks中删除 cancelled:^{ SDWebImageDownloader *sself = wself; if (!sself) return; dispatch_barrier_async(sself.barrierQueue, ^{ [sself.URLCallbacks removeObjectForKey:url]; }); }]; operation.shouldDecompressImages = wself.shouldDecompressImages; // operation的认证配置 if (wself.username && wself.password) { operation.credential = [NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession]; } // operation的优先级配置 if (options & SDWebImageDownloaderHighPriority) { operation.queuePriority = NSOperationQueuePriorityHigh; } else if (options & SDWebImageDownloaderLowPriority) { operation.queuePriority = NSOperationQueuePriorityLow; } // 将操作加入到操作队列downloadQueue中 [wself.downloadQueue addOperation:operation]; // 如果是LIFO顺序,则将新的操作作为原队列中的最后一个操作的依赖,然后将新操作设置为最后一个操作。 if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) { [wself.lastAddedOperation addDependency:operation]; wself.lastAddedOperation = operation; } }]; return operation; }
上面的方法调用了addProgressCallback:andCompletedBlock:forURL:createCallback:方法来将请求的信息存入下载器
- (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock andCompletedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageNoParamsBlock)createCallback {
// URL作为调用字典URLCallbacks的键值,不能为空。如若为空的话,会立即调用完成block,没有图像或者数据。
if (url == nil) {
if (completedBlock != nil) {
completedBlock(nil, nil, nil, NO);
}
return;
}
// 以dispatch_barrier_synv操作为保证同一时间只有一个线程能对URLCallbacks进行操作。
dispatch_barrier_sync(self.barrierQueue, ^{
BOOL first = NO;
if (!self.URLCallbacks[url]) {
self.URLCallbacks[url] = [NSMutableArray new];
first = YES;
}
// 处理同一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();
}
});
}
下载操作
每个图片的下载都是一个Operation操作。我们在上面分析过这个操作的创建及加入操作队列的过程。现在我们来看看单个操作的具体实现。SDWebImage定义了一个协议,即SDWebImageOperation作为图片下载操作的基础协议。它只声明了一个cancel方法,用于取消操作。协议的具体声明如下:
@protocol SDWebImageOperation <NSObject> - (void)cancel; @end
SDWebImage自定义了一个Operation类,即SDWebImageDownloaderOperation,它继承自NSOperation,并采用了SDWebImageOperation协议。除了继承而来的方法,该类只向外暴露了一个方法,即上面所用到的初始化方法initWithRequest:options:pregress:completed:cancelled:。
对于图片的下载,SDWebImageDownloaderOperation完全依赖于URL加载系统中的NSURLConnection类(并未使用iOS7以后的NSURLSession类)。我们先来分析一下SDWebImageDownloaderOperation类中对于图片实际数据的下载处理,即NSURLConnection各代理方法的实现。
首先,SDWebImageDownloaderOperation在Extention中采用了NSURLConnectionDataDelegate协议,并实现了协议的以下几个方法
connection:didReceiveResponse: connection:didReceiveData: connectionDidFinishLoading: connection:didFailWithError: connection:willCacheResponse: connectionShouldUseCredentialStorage: connection:willSendRequestForAuthenticationChallenge:
这些方法我们就不逐一分析了,就终点分析一下connection:didReceiveResponse:和connection:didReceiveData:两个方法。
connection:didReceiveResponse方法通过判断NSURLResponse的实际类型和状态码,对除304以外400以内的状态码反应。
- (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]; // 当服务器返回'304 Not Modified'的时候,意味着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:方法的主要任务是接受数据。每次接收到数据时,都会用现有的数据创建一个CGImageSourceRef对象以作处理。在首次获取到数据时(width+height==0)会从这些包含图像信息的数据中取出图像的长、宽、方向等信息以备使用。而后在图片下载完成之前,会使用CGImageSourceRef对象创建一个图像对象,经过缩放、解压缩操作后生成一个UIImage对象供完成回调使用。当然,在这个方法中还需要处理的就是进度信息。如果我们有设置进度回调的话,就调用进度回调以处理当前图片的下载进度。
- (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 // 获得已经下载的总共字节数 const NSInteger totalSize = self.imageData.length; // 更新数据源,我们必须传递所有的数据,而不只是新的字节 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); // 当绘制到Core Graphics时,我们会丢失方向信息,这意味着有时候由initWithCGIImage创建的 // 图片的方向不对。(不像在connectionDidFinishLoading中用initWithData创建的图片) // 所以在这边我们先保存这个信息并在后面使用 orientation = [[self class] orientationFromPropertyValue:(orientationValue == -1 ? 1 : orientationValue)]; } } // 图片还未下载完成 if (width + height > 0 && totalSize < self.expectedSize) { // 使用现有的数据创建图片对象,如果数据中存有多张图片,则取第一张 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 // 对图片进行缩放、解码操作 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类。它没有简单的实现main方法,而是采用更加灵活的start方法,以便自己管理下载的状态。
在start方法中,创建了我们下载所使用的NSURLConnection对象,开启了图片的下载,同时抛出一个下载开始的通知。start方法的具体实现如下:
- (void)start { @synchronized (self) { //管理下载状态,如果已取消,则重置当前下载并设置完成状态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)]; // 如果设置了在后台执行,则进行后台执行 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]; }); // 确保我们的后台线程启动runloop,这样就能处理下载的数据。 // Fail、Finish或者cancel的时候CFRunloopStop(CFRunloopGetCurrent())关闭runloop。如果不写 // CFRunloopRun(),根本不会执行NSURLConnection的代理方法的,因为该线程没开启runloop,马上就完了。 // runloop相当于子线程循环,可以灵活控制子线程的生命周期。 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 }
当然,在下载完成或者下载失败后,需要停止当前线程的runloop,清除连接,并抛出下载停止的通知。如果下载成功,则会处理完整的图片数据,对其进行适当的缩放与解压缩操作,以提供给完成回调使用。具体可参考-connectionDidFinishLoading:与-connection:didFailWithError:的实现。
小结
下载的核心其实就是利用NSURLConnection对象来加载数据。每个图片的下载都由一个Operation操作来完成,并将这些操作放到一个操作队列中。这样可以实现图片的并发下载。相关文章推荐
- ios后台长时间运行
- 三目运算iOS
- GCD 多线程 ---的记录 iOS
- <iOS> The operation could not be performed because of one or more tree conflicts.
- 保持iOS设备屏幕常亮的方法
- ios深拷贝,浅拷贝,拷贝自定义对象的简单介绍
- iOS 修改导航栏的返回按钮的内容
- iOS图片设置圆角的三种方式
- IOS总结
- iOS 保持界面流畅的技巧
- iOS 横竖屏控制
- iOS开发之实现毛玻璃效果及图片模糊效果
- iOS字符串操作
- iOS编程修改系统音量
- ios引导页 设定以及 图片尺寸
- iOS7或以后将不能使用MAC地址生成设备的唯一标识
- 定制ios7中的导航栏和状态栏
- iOS文件操作
- 在开发iOS程序时对日期处理的总结(转)
- Android IOS WebRTC 音视频开发总结(六八)-- Google: What's next for WebRTC