这里分析的开源库及版本
AFNetworking:3.0
SDWebImage:4.0
一. AFURLSessionManager
中的 completionQueue
和 completionGroup
在使用时,可以通过外部传入,也可以直接使用框架提供的group私有单例或者主队列。
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, error);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
这段代码是在 URLSession:task:didCompleteWithError
中执行的,意思是将自定义的完成回调放在 completionQueue
上执行。由于要发已完成的通知,所以放在主线程上执行发通知的操作。
这里并没有使用 dispatch_group_notify
和 dispatch_group_wait
来同步,那么使用 group
的意义在哪呢?一开始我也不太明白,这里有人给出了见解,但是外部使用者如果认为 completionGroup
属性可读,而直接使用它来通知也会出现问题,因为内部使用的是私有单例,所以这块为啥用 group
还是有些困惑的。
二. AFURLSessionManager
中的 url_session_manager_processing_queue
dispatch_async(url_session_manager_processing_queue(), ^{
NSError *serializationError = nil;
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
...
});
url_session_manager_processing_queue
是框架提供的自定义并发队列单例,这段代码也是在 URLSession:task:didCompleteWithError
中执行的,意思是将反序列化的操作放在并发队列上执行。
三. dispatch_semaphore_t
- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
__block NSArray *tasks = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
tasks = dataTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
tasks = uploadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
tasks = downloadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return tasks;
}
使用信号量来同步读取。其实也可以用 dispatch_sync
。
四. NSLock
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
NSParameterAssert(task);
NSParameterAssert(delegate);
[self.lock lock];
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
[self addNotificationObserverForTask:task];
[self.lock unlock];
}
由于 mutableTaskDelegatesKeyedByTaskIdentifier
是字典,多线程读取字典要加锁。其实也可以用 dispatch_barrier_async
。
五. synchronized
SDWebImageManager
中的 failedURLs
是一个集合。
@synchronized (self.failedURLs) {
isFailedUrl = [self.failedURLs containsObject:url];
}
@synchronized (self.failedURLs) {
[self.failedURLs addObject:url];
}
@synchronized (self.failedURLs) {
[self.failedURLs removeObject:url];
}
这个同步机制没什么可说的。
六. 读写操作
AFHTTPRequestSerializer
中的 requestHeaderModificationQueue
是个并发队列,mutableHTTPRequestHeaders
是个字典,针对字典同时读是可以的,但是同时写或者同时读写是不可以的。
- (NSDictionary *)HTTPRequestHeaders {
NSDictionary __block *value;
dispatch_sync(self.requestHeaderModificationQueue, ^{
value = [NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders];
});
return value;
}
- (NSString *)valueForHTTPHeaderField:(NSString *)field {
NSString __block *value;
dispatch_sync(self.requestHeaderModificationQueue, ^{
value = [self.mutableHTTPRequestHeaders valueForKey:field];
});
return value;
}
这两个操作都是字典的读操作,是可以同时读的。
- (void)setValue:(NSString *)value
forHTTPHeaderField:(NSString *)field
{
dispatch_barrier_async(self.requestHeaderModificationQueue, ^{
[self.mutableHTTPRequestHeaders setValue:value forKey:field];
});
}
- (void)clearAuthorizationHeader {
dispatch_barrier_async(self.requestHeaderModificationQueue, ^{
[self.mutableHTTPRequestHeaders removeObjectForKey:@"Authorization"];
});
}
这两个操作都是字典的写操作,必须与其他操作互斥。
再来看 AFAutoPurgingImageCache
中的例子,synchronizationQueue
是一个并发队列,cachedImages
是字典,currentMemoryUsage
是整数。
- (UInt64)memoryUsage {
__block UInt64 result = 0;
dispatch_sync(self.synchronizationQueue, ^{
result = self.currentMemoryUsage;
});
return result;
}
- (nullable UIImage *)imageWithIdentifier:(NSString *)identifier {
__block UIImage *image = nil;
dispatch_sync(self.synchronizationQueue, ^{
AFCachedImage *cachedImage = self.cachedImages[identifier];
image = [cachedImage accessImage];
});
return image;
}
这两个操作都是读操作,是可以同时读的。
- (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier {
dispatch_barrier_async(self.synchronizationQueue, ^{
AFCachedImage *cacheImage = [[AFCachedImage alloc] initWithImage:image identifier:identifier];
AFCachedImage *previousCachedImage = self.cachedImages[identifier];
if (previousCachedImage != nil) {
self.currentMemoryUsage -= previousCachedImage.totalBytes;
}
self.cachedImages[identifier] = cacheImage;
self.currentMemoryUsage += cacheImage.totalBytes;
});
...
}
- (BOOL)removeImageWithIdentifier:(NSString *)identifier {
__block BOOL removed = NO;
dispatch_barrier_sync(self.synchronizationQueue, ^{
AFCachedImage *cachedImage = self.cachedImages[identifier];
if (cachedImage != nil) {
[self.cachedImages removeObjectForKey:identifier];
self.currentMemoryUsage -= cachedImage.totalBytes;
removed = YES;
}
});
return removed;
}
这两个操作都是字典写操作,必须与其他操作互斥。注意这里如果当前线程需要使用 block
中返回的数据,需要使用 dispatch_barrier_sync
。
在看 SDWebImageDownloaderOperation
中的例子,同样,barrierQueue
是并发队列,callbackBlocks
是数组。
- (nullable NSArray *)callbacksForKey:(NSString *)key {
__block NSMutableArray *callbacks = nil;
dispatch_sync(self.barrierQueue, ^{
callbacks = [[self.callbackBlocks valueForKey:key] mutableCopy];
[callbacks removeObjectIdenticalTo:[NSNull null]];
});
return [callbacks copy]; // strip mutability here
}
这个操作是字典的读操作。
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
dispatch_barrier_async(self.barrierQueue, ^{
[self.callbackBlocks addObject:callbacks];
});
return callbacks;
}
- (BOOL)cancel:(nullable id)token {
__block BOOL shouldCancel = NO;
dispatch_barrier_sync(self.barrierQueue, ^{
[self.callbackBlocks removeObjectIdenticalTo:token];
if (self.callbackBlocks.count == 0) {
shouldCancel = YES;
}
});
if (shouldCancel) {
[self cancel];
}
return shouldCancel;
}
这两个操作都是字典写操作,必须与其他操作互斥。
总结
什么时候用sync、什么时候用async?
如果当前线程后续的操作需要用到block
中的数据时,就用sync,否则用async。什么时候用串行队列、什么时候用并发队列?
如果block
中的操作需要互斥的进行(比如写字典、文件IO),就用串行队列,否则用并发队列。
PS:
同步机制还包括 NSRecursiveLock
,NSConditionLock
,NSCondition
,OSSpinLock
,pthread_mutex
等,后续会单独出一篇分析锁的文章。