[关闭]
@qidiandasheng 2022-08-07T13:42:01.000000Z 字数 19273 阅读 75

多线程使用案例(❎)

iOS实战


数据持久化

如果在主线程中存储数据,数据量比较大时会阻塞主线程造成页面卡顿。需要新开线程在后台处理。

然而新开的线程过多也会引起过多的内存和线程上下文切换的性能损耗问题,所以类似数据库这种需要频繁读写磁盘操作的任务,尽量使用串行队列来管理,避免因为多线程并发而出现内存问题。

在进行数据读写操作时,总是需要一段时间来等待磁盘响应的,如果在这个时候通过 GCD 发起了一个任务,那么 GCD 就会本着最大化利用 CPU 的原则,会在等待磁盘响应的这个空档,再创建一个新线程来保证能够充分利用 CPU。

而如果 GCD 发起的这些新任务,都是类似于数据存储这样需要等待磁盘响应的任务的话,那么随着任务数量的增加,GCD 创建的新线程就会越来越多,从而导致内存资源越来越紧张,等到磁盘开始响应后,再读取数据又会占用更多的内存。结果就是,失控的内存占用会引起更多的内存问题。

IM数据库写入

Hermes项目里使用异步串行队列来进行数据库的写入。

  1. //创建串行队列
  2. static let writeQueue = DispatchQueue(label: "chat.rocket.realm.write", qos: .background)
  3. public func execute(_ execution: @escaping (Realm) -> Void, completion: VoidCompletion? = nil) {
  4. var backgroundTaskId: UIBackgroundTaskIdentifier?
  5. //创建后台任务,当app退入后台时也能继续执行一小段时间
  6. backgroundTaskId = UIApplication.shared.beginBackgroundTask(withName: "chat.rocket.realm.background") {
  7. backgroundTaskId = UIBackgroundTaskInvalid
  8. }
  9. if let backgroundTaskId = backgroundTaskId {
  10. let config = self.configuration
  11. //在异步串行队列里执行数据库写入任务
  12. Realm.writeQueue.async {
  13. if let realm = try? Realm(configuration: config) {
  14. try? realm.write {
  15. execution(realm)
  16. }
  17. }
  18. if let completion = completion {
  19. DispatchQueue.main.async {
  20. completion()
  21. }
  22. }
  23. UIApplication.shared.endBackgroundTask(backgroundTaskId)
  24. }
  25. }
  26. }

SDImageCache

SDImageCacheSDWebImage的缓存处理模块。SDImageCache分为内存缓存和磁盘缓存。

内存缓存通过一个信号量锁来处理多线程安全问题:

  1. - (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g {
  2. [super setObject:obj forKey:key cost:g];
  3. if (!self.config.shouldUseWeakMemoryCache) {
  4. return;
  5. }
  6. if (key && obj) {
  7. // Store weak cache
  8. LOCK(self.weakCacheLock);
  9. [self.weakCache setObject:obj forKey:key];
  10. UNLOCK(self.weakCacheLock);
  11. }
  12. }
  13. - (id)objectForKey:(id)key {
  14. id obj = [super objectForKey:key];
  15. if (!self.config.shouldUseWeakMemoryCache) {
  16. return obj;
  17. }
  18. if (key && !obj) {
  19. // Check weak cache
  20. LOCK(self.weakCacheLock);
  21. obj = [self.weakCache objectForKey:key];
  22. UNLOCK(self.weakCacheLock);
  23. if (obj) {
  24. // Sync cache
  25. NSUInteger cost = 0;
  26. if ([obj isKindOfClass:[UIImage class]]) {
  27. cost = SDCacheCostForImage(obj);
  28. }
  29. [super setObject:obj forKey:key cost:cost];
  30. }
  31. }
  32. return obj;
  33. }
  34. - (void)removeObjectForKey:(id)key {
  35. [super removeObjectForKey:key];
  36. if (!self.config.shouldUseWeakMemoryCache) {
  37. return;
  38. }
  39. if (key) {
  40. // Remove weak cache
  41. LOCK(self.weakCacheLock);
  42. [self.weakCache removeObjectForKey:key];
  43. UNLOCK(self.weakCacheLock);
  44. }
  45. }

磁盘缓存通过一个异步串行队列来读写数据:

  1. //创建串行队列
  2. _ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);
  1. //写入本地磁盘
  2. - (void)storeImage:(nullable UIImage *)image
  3. imageData:(nullable NSData *)imageData
  4. forKey:(nullable NSString *)key
  5. toDisk:(BOOL)toDisk
  6. completion:(nullable SDWebImageNoParamsBlock)completionBlock {
  7. if (!image || !key) {
  8. if (completionBlock) {
  9. completionBlock();
  10. }
  11. return;
  12. }
  13. // if memory cache is enabled
  14. if (self.config.shouldCacheImagesInMemory) {
  15. NSUInteger cost = SDCacheCostForImage(image);
  16. [self.memCache setObject:image forKey:key cost:cost];
  17. }
  18. if (toDisk) {
  19. dispatch_async(self.ioQueue, ^{
  20. @autoreleasepool {
  21. NSData *data = imageData;
  22. if (!data && image) {
  23. // If we do not have any data to detect image format, check whether it contains alpha channel to use PNG or JPEG format
  24. SDImageFormat format;
  25. if (SDCGImageRefContainsAlpha(image.CGImage)) {
  26. format = SDImageFormatPNG;
  27. } else {
  28. format = SDImageFormatJPEG;
  29. }
  30. data = [[SDWebImageCodersManager sharedInstance] encodedDataWithImage:image format:format];
  31. }
  32. [self _storeImageDataToDisk:data forKey:key];
  33. }
  34. if (completionBlock) {
  35. dispatch_async(dispatch_get_main_queue(), ^{
  36. completionBlock();
  37. });
  38. }
  39. });
  40. } else {
  41. if (completionBlock) {
  42. completionBlock();
  43. }
  44. }
  45. }
  1. //从本地磁盘读取
  2. - (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock {
  3. dispatch_async(self.ioQueue, ^{
  4. BOOL exists = [self _diskImageDataExistsWithKey:key];
  5. if (completionBlock) {
  6. dispatch_async(dispatch_get_main_queue(), ^{
  7. completionBlock(exists);
  8. });
  9. }
  10. });
  11. }

耗时代码处理

如果使用多次数的循环语句,或者是使用非常耗时的api时,会影响到主线程导致卡顿。可以新开线程在后台处理,然后如果有需要刷新UI则在主线程中同步。

长链接消息接收回调

Hermes项目里websocket长链接会接到很多消息的回调,这些回调可能会拖慢主线程的时间,所以放入一个异步线程串行的执行消息回调。

  1. //创建一个串行队列,优先级为background
  2. static let messageHandlerQueue = DispatchQueue(label: "chat.rocket.websocket.handler", qos: .background)
  3. public func websocketDidReceiveMessage(socket: WebSocket, text: String) {
  4. let json = JSON(parseJSON: text)
  5. // JSON is invalid
  6. guard json.exists() else {
  7. Log.debug("[WebSocket] \(socket.currentURL)\n - did receive invalid JSON object:\n\(text)")
  8. return
  9. }
  10. if let raw = json.rawString() {
  11. Log.debug("[WebSocket] \(socket.currentURL)\n - did receive JSON message:\n\(raw)")
  12. }
  13. //收到websocket的消息时异步的串行执行回调任务
  14. SocketManager.messageHandlerQueue.async {
  15. self.handleMessage(json, socket: socket)
  16. }
  17. }

异步绘制

  1. static dispatch_queue_t YYAsyncLayerGetDisplayQueue() {
  2. //最大队列数量
  3. #define MAX_QUEUE_COUNT 16
  4. //队列数量
  5. static int queueCount;
  6. //使用栈区的数组存储队列
  7. static dispatch_queue_t queues[MAX_QUEUE_COUNT];
  8. static dispatch_once_t onceToken;
  9. static int32_t counter = 0;
  10. dispatch_once(&onceToken, ^{
  11. //要点 1 :串行队列数量和处理器数量相同
  12. queueCount = (int)[NSProcessInfo processInfo].activeProcessorCount;
  13. queueCount = queueCount < 1 ? 1 : queueCount > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : queueCount;
  14. //要点 2 :创建串行队列,设置优先级
  15. if ([UIDevice currentDevice].systemVersion.floatValue >= 8.0) {
  16. for (NSUInteger i = 0; i < queueCount; i++) {
  17. dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0);
  18. queues[i] = dispatch_queue_create("com.ibireme.yykit.render", attr);
  19. }
  20. } else {
  21. for (NSUInteger i = 0; i < queueCount; i++) {
  22. queues[i] = dispatch_queue_create("com.ibireme.yykit.render", DISPATCH_QUEUE_SERIAL);
  23. dispatch_set_target_queue(queues[i], dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
  24. }
  25. }
  26. });
  27. //要点 3 :轮询返回队列
  28. int32_t cur = OSAtomicIncrement32(&counter);
  29. if (cur < 0) cur = -cur;
  30. return queues[(cur) % queueCount];
  31. #undef MAX_QUEUE_COUNT
  32. }
  33. dispatch_async(YYAsyncLayerGetDisplayQueue(), ^{
  34. //绘制
  35. UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
  36. .....
  37. UIGraphicsEndImageContext();
  38. //回到主线程显示
  39. dispatch_async(dispatch_get_main_queue(), ^{
  40. if (isCancelled()) {
  41. if (task.didDisplay) task.didDisplay(self, NO);
  42. } else {
  43. self.contents = (__bridge id)(image.CGImage);
  44. if (task.didDisplay) task.didDisplay(self, YES);
  45. }
  46. });
  47. }

或者我认为可以使用NSOperationQueue

  1. #define MAX_QUEUE_COUNT 16
  2. static int queueCount;
  3. queueCount = (int)[NSProcessInfo processInfo].activeProcessorCount;
  4. queueCount = queueCount < 1 ? 1 : queueCount > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : queueCount;
  5. NSOperationQueue *queue = [[NSOperationQueue alloc] init];
  6. queue.maxConcurrentOperationCount = queueCount;
  7. NSBlockOperation *p = [NSBlockOperation blockOperationWithBlock:^{
  8. //绘制
  9. UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
  10. .....
  11. UIGraphicsEndImageContext();
  12. //回到主线程显示
  13. dispatch_async(dispatch_get_main_queue(), ^{
  14. if (isCancelled()) {
  15. if (task.didDisplay) task.didDisplay(self, NO);
  16. } else {
  17. self.contents = (__bridge id)(image.CGImage);
  18. if (task.didDisplay) task.didDisplay(self, YES);
  19. }
  20. });
  21. }];
  22. [queue addOperation:p];

网络请求等待

主线程异步调用请求问题?

原因一:

如果我们放到主线程去做,势必要这么写:

  1. [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES]

这样NSURLConnection的回调会被放在主线程中NSDefaultRunLoopMode中,这样我们在其它类似UITrackingRunLoopMode模式下,我们是得不到网络请求的结果的,这显然不是我们想要的,那么我们势必需要调用:

  1. [connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];

把它加入NSRunLoopCommonModes中,试想如果有大量的网络请求,同时回调回来,就会影响我们的UI体验了。

原因二:

如果我们请求数据返回,势必要进行数据解析,解析成我们需要的格式,那么这些解析都在主线程中做,给主线程增加额外的负担。

又或者我们回调回来开辟一个新的线程去做数据解析,那么我们有n个请求回来开辟n条线程带来的性能损耗,以及线程间切换带来的损耗,是不是一笔更大的开销。

开辟n条线程做请求问题?

如果开发n条线程做请求的话,那就需要设置runloop保活住线程,等待结果回调。为了等待不确定的请求结果,阻塞住线程,白白浪费n条线程的开销。

AFNetworking3.0初始化

  1. - (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
  2. self = [super init];
  3. if (!self) {
  4. return nil;
  5. }
  6. .....
  7. self.operationQueue = [[NSOperationQueue alloc] init];
  8. self.operationQueue.maxConcurrentOperationCount = 1;
  9. .....
  10. }
  11. - (NSURLSession *)session {
  12. @synchronized (self) {
  13. if (!_session) {
  14. _session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
  15. }
  16. }
  17. return _session;
  18. }

这个operationQueue就是我们代理回调的queue。这里把代理回调的线程并发数设置为1了。

这里的并发数仅仅是回调代理的线程并发数。而不是请求网络的线程并发数。请求网络是由NSUrlSession来做的,它内部维护了一个线程池,用来做网络请求。它调度线程,基于底层的CFSocket去发送请求和接收数据。这些线程是并发的。

AFNetworking3.0回调

  1. static dispatch_queue_t url_session_manager_processing_queue() {
  2. static dispatch_queue_t af_url_session_manager_processing_queue;
  3. static dispatch_once_t onceToken;
  4. dispatch_once(&onceToken, ^{
  5. af_url_session_manager_processing_queue = dispatch_queue_create("com.alamofire.networking.session.manager.processing", DISPATCH_QUEUE_CONCURRENT);
  6. });
  7. return af_url_session_manager_processing_queue;
  8. }
  1. - (void)URLSession:(__unused NSURLSession *)session
  2. task:(NSURLSessionTask *)task
  3. didCompleteWithError:(NSError *)error
  4. {
  5. if (error) {
  6. userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
  7. dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
  8. if (self.completionHandler) {
  9. self.completionHandler(task.response, responseObject, error);
  10. }
  11. dispatch_async(dispatch_get_main_queue(), ^{
  12. [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
  13. });
  14. });
  15. } else {
  16. dispatch_async(url_session_manager_processing_queue(), ^{
  17. #并发队列数据解析
  18. ......
  19. dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
  20. if (self.completionHandler) {
  21. self.completionHandler(task.response, responseObject, serializationError);
  22. }
  23. dispatch_async(dispatch_get_main_queue(), ^{
  24. [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
  25. });
  26. });
  27. });
  28. }
  29. }

按照初始化设置,这里的回调是串行回调回来的,只会开辟一条线程,没必要为不确定的网络请求回调开辟多条线程,如果网络一直在等待的话那就会浪费线程资源。

当数据串行回调回来之后,就使用并发队列创建多条线程来进行数据的解析了,最后放到一个GCD group里面,使用者可以自己设置group,那就可以监听所有的回调完成。

AF3.x的整个流程和线程的关系

加载网络资源

SDWebImage的异步下载

SDWebImageDownloader为图片下载器对象,里面主要管理着SDWebImageDownloaderOperation进行对图片的下载。

SDWebImageDownloaderOperation

每一个图片下载都是一个SDWebImageDownloaderOperation操作,SDWebImageDownloaderOperation继承自NSOperation,内部创建NSURLSessionTask来进行图片下载的请求(NSURLSessionSDWebImageDownloader传入)。

  1. - (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
  2. inSession:(nullable NSURLSession *)session
  3. options:(SDWebImageDownloaderOptions)options {
  4. if ((self = [super init])) {
  5. _request = [request copy];
  6. _shouldDecompressImages = YES;
  7. _options = options;
  8. _callbackBlocks = [NSMutableArray new];
  9. _executing = NO;
  10. _finished = NO;
  11. _expectedSize = 0;
  12. _unownedSession = session;
  13. _callbacksLock = dispatch_semaphore_create(1);
  14. _coderQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationCoderQueue", DISPATCH_QUEUE_SERIAL);
  15. }
  16. return self;
  17. }
  18. - (void)start {
  19. @synchronized (self) {
  20. if (self.isCancelled) {
  21. self.finished = YES;
  22. [self reset];
  23. return;
  24. }
  25. #if SD_UIKIT
  26. Class UIApplicationClass = NSClassFromString(@"UIApplication");
  27. BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
  28. if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
  29. __weak __typeof__ (self) wself = self;
  30. UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
  31. self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
  32. __strong __typeof (wself) sself = wself;
  33. if (sself) {
  34. [sself cancel];
  35. [app endBackgroundTask:sself.backgroundTaskId];
  36. sself.backgroundTaskId = UIBackgroundTaskInvalid;
  37. }
  38. }];
  39. }
  40. #endif
  41. NSURLSession *session = self.unownedSession;
  42. if (!session) {
  43. NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
  44. sessionConfig.timeoutIntervalForRequest = 15;
  45. /**
  46. * Create the session for this task
  47. * We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
  48. * method calls and completion handler calls.
  49. */
  50. session = [NSURLSession sessionWithConfiguration:sessionConfig
  51. delegate:self
  52. delegateQueue:nil];
  53. self.ownedSession = session;
  54. }
  55. if (self.options & SDWebImageDownloaderIgnoreCachedResponse) {
  56. // Grab the cached data for later check
  57. NSURLCache *URLCache = session.configuration.URLCache;
  58. if (!URLCache) {
  59. URLCache = [NSURLCache sharedURLCache];
  60. }
  61. NSCachedURLResponse *cachedResponse;
  62. // NSURLCache's `cachedResponseForRequest:` is not thread-safe, see https://developer.apple.com/documentation/foundation/nsurlcache#2317483
  63. @synchronized (URLCache) {
  64. cachedResponse = [URLCache cachedResponseForRequest:self.request];
  65. }
  66. if (cachedResponse) {
  67. self.cachedData = cachedResponse.data;
  68. }
  69. }
  70. self.dataTask = [session dataTaskWithRequest:self.request];
  71. self.executing = YES;
  72. }
  73. if (self.dataTask) {
  74. #pragma clang diagnostic push
  75. #pragma clang diagnostic ignored "-Wunguarded-availability"
  76. if ([self.dataTask respondsToSelector:@selector(setPriority:)]) {
  77. if (self.options & SDWebImageDownloaderHighPriority) {
  78. self.dataTask.priority = NSURLSessionTaskPriorityHigh;
  79. } else if (self.options & SDWebImageDownloaderLowPriority) {
  80. self.dataTask.priority = NSURLSessionTaskPriorityLow;
  81. }
  82. }
  83. #pragma clang diagnostic pop
  84. [self.dataTask resume];
  85. for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
  86. progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
  87. }
  88. __weak typeof(self) weakSelf = self;
  89. dispatch_async(dispatch_get_main_queue(), ^{
  90. [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:weakSelf];
  91. });
  92. } else {
  93. [self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorUnknown userInfo:@{NSLocalizedDescriptionKey : @"Task can't be initialized"}]];
  94. [self done];
  95. return;
  96. }
  97. #if SD_UIKIT
  98. Class UIApplicationClass = NSClassFromString(@"UIApplication");
  99. if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
  100. return;
  101. }
  102. if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
  103. UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)];
  104. [app endBackgroundTask:self.backgroundTaskId];
  105. self.backgroundTaskId = UIBackgroundTaskInvalid;
  106. }
  107. #endif
  108. }

SDWebImageDownloader

SDWebImageDownloader维护着一个下载队列downloadQueue,默认最大并发数为6,也可以自定义。

  1. - (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration {
  2. if ((self = [super init])) {
  3. _operationClass = [SDWebImageDownloaderOperation class];
  4. _shouldDecompressImages = YES;
  5. _executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
  6. _downloadQueue = [NSOperationQueue new];
  7. _downloadQueue.maxConcurrentOperationCount = 6;
  8. _downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
  9. _URLOperations = [NSMutableDictionary new];
  10. SDHTTPHeadersMutableDictionary *headerDictionary = [SDHTTPHeadersMutableDictionary dictionary];
  11. NSString *userAgent = nil;
  12. .......
  13. _HTTPHeaders = headerDictionary;
  14. _operationsLock = dispatch_semaphore_create(1);
  15. _headersLock = dispatch_semaphore_create(1);
  16. _downloadTimeout = 15.0;
  17. //创建NSURLSession
  18. [self createNewSessionWithConfiguration:sessionConfiguration];
  19. }
  20. return self;
  21. }
  1. //创建对应的operation放入下载队列中(初始化创建的NSURLSession传入每一个operation中)
  2. - (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
  3. options:(SDWebImageDownloaderOptions)options
  4. progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
  5. completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
  6. // 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.
  7. if (url == nil) {
  8. if (completedBlock != nil) {
  9. completedBlock(nil, nil, nil, NO);
  10. }
  11. return nil;
  12. }
  13. LOCK(self.operationsLock);
  14. NSOperation<SDWebImageDownloaderOperationInterface> *operation = [self.URLOperations objectForKey:url];
  15. // There is a case that the operation may be marked as finished, but not been removed from `self.URLOperations`.
  16. if (!operation || operation.isFinished) {
  17. operation = [self createDownloaderOperationWithUrl:url options:options];
  18. __weak typeof(self) wself = self;
  19. operation.completionBlock = ^{
  20. __strong typeof(wself) sself = wself;
  21. if (!sself) {
  22. return;
  23. }
  24. LOCK(sself.operationsLock);
  25. [sself.URLOperations removeObjectForKey:url];
  26. UNLOCK(sself.operationsLock);
  27. };
  28. [self.URLOperations setObject:operation forKey:url];
  29. // Add operation to operation queue only after all configuration done according to Apple's doc.
  30. // `addOperation:` does not synchronously execute the `operation.completionBlock` so this will not cause deadlock.
  31. [self.downloadQueue addOperation:operation];
  32. }
  33. UNLOCK(self.operationsLock);
  34. id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
  35. SDWebImageDownloadToken *token = [SDWebImageDownloadToken new];
  36. token.downloadOperation = operation;
  37. token.url = url;
  38. token.downloadOperationCancelToken = downloadOperationCancelToken;
  39. return token;
  40. }

SDWebImageDownloader中实现NSURLSessionDataDelegateNSURLSessionTaskDelegate委托方法,然后根据回调的taskid找到对应的operation,执行对应的operation里的回调方法:

  1. #pragma mark NSURLSessionDataDelegate
  2. .......
  3. #pragma mark NSURLSessionTaskDelegate
  4. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
  5. // Identify the operation that runs this task and pass it the delegate method
  6. NSOperation<SDWebImageDownloaderOperationInterface> *dataOperation = [self operationWithTask:task];
  7. if ([dataOperation respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]) {
  8. [dataOperation URLSession:session task:task didCompleteWithError:error];
  9. }
  10. }

任务组和栅栏

可使用任务组和栅栏处理多个异步操作完成之后再执行后面的操作的情况。比如获取两个异步请求数据后才渲染界面的情况。

  1. dispatch_group_t group = dispatch_group_create();
  2. dispatch_group_enter(group);
  3. NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
  4. NSLog(@"A");
  5. dispatch_group_leave(group);
  6. }] ;
  7. [task resume];
  8. dispatch_group_enter(group);
  9. NSURLSessionDataTask *task2 = [[NSURLSession sharedSession] dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
  10. NSLog(@"B");
  11. dispatch_group_leave(group);
  12. }] ;
  13. [task2 resume];
  14. dispatch_group_notify(group, dispatch_get_main_queue(), ^{
  15. NSLog(@"all end");
  16. });
  1. dispatch_queue_t queue = dispatch_queue_create(0, DISPATCH_QUEUE_CONCURRENT);
  2. dispatch_async(queue, ^{
  3. __block BOOL isExecuted = NO;
  4. NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
  5. NSLog(@"A");
  6. isExecuted = YES;
  7. }] ;
  8. [task resume];
  9. while (isExecuted == NO) {
  10. [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
  11. }
  12. });
  13. dispatch_async(queue, ^{
  14. __block BOOL isExecuted = NO;
  15. NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
  16. NSLog(@"B");
  17. isExecuted = YES;
  18. }] ;
  19. [task resume];
  20. while (isExecuted == NO) {
  21. [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
  22. }
  23. });
  24. //让barrier之前的线程执行完成之后才会执行barrier后面的操作
  25. dispatch_barrier_async(queue, ^{
  26. __block BOOL isExecuted = NO;
  27. NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
  28. NSLog(@"拿到了A的值");
  29. isExecuted = YES;
  30. }] ;
  31. [task resume];
  32. while (isExecuted == NO) {
  33. [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
  34. }
  35. });
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注