正文
在实际开发中,经常需要在几个任务全部执行完成之后,在执行后续操作,在 iOS 中,我们可以通过 NSOperation 等达到这一目的。在本篇文章中,我们会介绍如何通过 dispatch_group_enter 和 dispatch_group_leave 来实现这一功能,以及使用过程中遇到的坑。
如何使用
通过一个例子来看下如何使用 dispatch_group_enter 和 dispatch_group_leave:
- {
- // 首先 需要创建一个线程组
- dispatch_group_t group = dispatch_group_create();
- // 任务1
- dispatch_group_enter(group);
- NSURLSessionDataTask *task1 = [[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@""] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
- NSLog(@"任务1完成");
- dispatch_group_leave(group);
- }];
- [task1 resume];
- // 任务2
- dispatch_group_enter(group);
- NSURLSessionDataTask *task2 = [[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@""] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
- NSLog(@"任务2完成");
- dispatch_group_leave(group);
- }];
- [task2 resume];
- // 全部完成
- dispatch_group_notify(group, dispatch_get_main_queue(), ^(){
- NSLog(@"全部完成");
- });
- }
任务1和任务2执行完成之后,才会执行全部完成中的任务。
**注意:**在使用时,dispatch_group_enter 和 dispatch_group_leave 需要成对出现,如果 dispatch_group_leave 的调用次数多于 dispatch_group_enter 的调用次数,程序会 crash。相反,虽然不会发生 crash , 但可能不会达到预期效果。
crash 场景分析
使用场景是,需要异步获取多个图片封面,所有都获取完成后,在执行指定任务,代码示例如下:
- - (void)fetchCovers {
- dispatch_queue_t queue = dispatch_queue_create("com.demo.xxx", DISPATCH_QUEUE_CONCURRENT);
- dispatch_group_t group = dispatch_group_create();
- for (int i = 0; i < 40; ++i) {
- dispatch_group_enter(group);
- dispatch_async(queue, ^{
- [self fetchCoverByPHAsset:asset targetSize:CGSizeMake(200, 200) resultHandler:^(UIImage * _Nonnull, NSDictionary * _Nonnull, BOOL) {
- dispatch_group_leave(group);
- }];
- });
- }
- dispatch_group_notify(group, dispatch_get_main_queue(), ^(){
- // 全部完成后 执行指定任务
- });
- }
- - (void)fetchCover:(PHAsset *)asset targetSize:(CGSize)targeSize resultHandler:(void (^)(UIImage * _Nonnull, NSDictionary * _Nonnull, BOOL))resultHandler {
- PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
- if (@available(iOS 14.0, *)) {
- options.version = PHImageRequestOptionsVersionCurrent;
- }
- options.networkAccessAllowed = YES;
- [[PHImageManager defaultManager] requestImageForAsset:asset
- targetSize:targeSize
- contentMode:PHImageContentModeAspectFill
- options:options
- resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
- if (resultHandler) {
- resultHandler(result, info, [[info objectForKey:PHImageResultIsDegradedKey] boolValue]);
- }
- }];
- }
这里有个小 tips : 在使用 requestImageForAsset 获取图片时,如果 options 的 deliveryMode 属性使用默认值,在异步获取图片时,其回调可能会走2次。解决方案是将其显示设置为 PHImageRequestOptionsDeliveryModeHighQualityFormat 或 PHImageRequestOptionsDeliveryModeFastFormat。
- options.deliveryMode = PHImageRequestOptionsDeliveryModeFastFormat; // 或 PHImageRequestOptionsDeliveryModeHighQualityFormat
因为获取图片封面的回调可能会走 2 次,从而导致 dispatch_group_leave 调用次数多于 dispatch_group_enter 的调用次数,因此可能会发生 crash。
源码实现
- void
- dispatch_group_enter(dispatch_group_t dg)
- {
- uint32_t old_bits = os_atomic_sub_orig2o(dg, dg_bits,
- DISPATCH_GROUP_VALUE_INTERVAL, acquire);
- uint32_t old_value = old_bits & DISPATCH_GROUP_VALUE_MASK;
- if (unlikely(old_value == 0)) {
- _dispatch_retain(dg); // <rdar://problem/22318411>
- }
- if (unlikely(old_value == DISPATCH_GROUP_VALUE_MAX)) {
- DISPATCH_CLIENT_CRASH(old_bits,
- "Too many nested calls to dispatch_group_enter()");
- }
- }
- void
- dispatch_group_leave(dispatch_group_t dg)
- {
- uint64_t new_state, old_state = os_atomic_add_orig2o(dg, dg_state,
- DISPATCH_GROUP_VALUE_INTERVAL, release);
- uint32_t old_value = (uint32_t)(old_state & DISPATCH_GROUP_VALUE_MASK);
- if (unlikely(old_value == DISPATCH_GROUP_VALUE_1)) {
- old_state += DISPATCH_GROUP_VALUE_INTERVAL;
- do {
- new_state = old_state;
- if ((old_state & DISPATCH_GROUP_VALUE_MASK) == 0) {
- new_state &= ~DISPATCH_GROUP_HAS_WAITERS;
- new_state &= ~DISPATCH_GROUP_HAS_NOTIFS;
- } else {
- new_state &= ~DISPATCH_GROUP_HAS_NOTIFS;
- }
- if (old_state == new_state) break;
- } while (unlikely(!os_atomic_cmpxchgv2o(dg, dg_state,
- old_state, new_state, &old_state, relaxed)));
- return _dispatch_group_wake(dg, old_state, true);
- }
- if (unlikely(old_value == 0)) {
- DISPATCH_CLIENT_CRASH((uintptr_t)old_value,
- "Unbalanced call to dispatch_group_leave()");
- }
- }
以上就是iOS GCD之dispatch_group_enter和dispatch_group_leave使用的详细内容,更多关于iOS GCD使用的资料请关注w3xue其它相关文章!