经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » iOS » 查看文章
iOS?GCD之dispatch_group_enter和dispatch_group_leave使用
来源:jb51  时间:2023/3/31 8:57:45  对本文有异议

正文

在实际开发中,经常需要在几个任务全部执行完成之后,在执行后续操作,在 iOS 中,我们可以通过 NSOperation 等达到这一目的。在本篇文章中,我们会介绍如何通过 dispatch_group_enterdispatch_group_leave 来实现这一功能,以及使用过程中遇到的坑。

如何使用

通过一个例子来看下如何使用 dispatch_group_enterdispatch_group_leave

  1. {
  2. // 首先 需要创建一个线程组
  3. dispatch_group_t group = dispatch_group_create();
  4. // 任务1
  5. dispatch_group_enter(group);
  6. NSURLSessionDataTask *task1 = [[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@""] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
  7. NSLog(@"任务1完成");
  8. dispatch_group_leave(group);
  9. }];
  10. [task1 resume];
  11. // 任务2
  12. dispatch_group_enter(group);
  13. NSURLSessionDataTask *task2 = [[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@""] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
  14. NSLog(@"任务2完成");
  15. dispatch_group_leave(group);
  16. }];
  17. [task2 resume];
  18. // 全部完成
  19. dispatch_group_notify(group, dispatch_get_main_queue(), ^(){
  20. NSLog(@"全部完成");
  21. });
  22. }

任务1和任务2执行完成之后,才会执行全部完成中的任务。

**注意:**在使用时,dispatch_group_enter 和 dispatch_group_leave 需要成对出现,如果 dispatch_group_leave 的调用次数多于 dispatch_group_enter 的调用次数,程序会 crash。相反,虽然不会发生 crash , 但可能不会达到预期效果。

crash 场景分析

使用场景是,需要异步获取多个图片封面,所有都获取完成后,在执行指定任务,代码示例如下:

  1. - (void)fetchCovers {
  2. dispatch_queue_t queue = dispatch_queue_create("com.demo.xxx", DISPATCH_QUEUE_CONCURRENT);
  3. dispatch_group_t group = dispatch_group_create();
  4. for (int i = 0; i < 40; ++i) {
  5. dispatch_group_enter(group);
  6. dispatch_async(queue, ^{
  7. [self fetchCoverByPHAsset:asset targetSize:CGSizeMake(200, 200) resultHandler:^(UIImage * _Nonnull, NSDictionary * _Nonnull, BOOL) {
  8. dispatch_group_leave(group);
  9. }];
  10. });
  11. }
  12. dispatch_group_notify(group, dispatch_get_main_queue(), ^(){
  13. // 全部完成后 执行指定任务
  14. });
  15. }
  16. - (void)fetchCover:(PHAsset *)asset targetSize:(CGSize)targeSize resultHandler:(void (^)(UIImage * _Nonnull, NSDictionary * _Nonnull, BOOL))resultHandler {
  17. PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
  18. if (@available(iOS 14.0, *)) {
  19. options.version = PHImageRequestOptionsVersionCurrent;
  20. }
  21. options.networkAccessAllowed = YES;
  22. [[PHImageManager defaultManager] requestImageForAsset:asset
  23. targetSize:targeSize
  24. contentMode:PHImageContentModeAspectFill
  25. options:options
  26. resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
  27. if (resultHandler) {
  28. resultHandler(result, info, [[info objectForKey:PHImageResultIsDegradedKey] boolValue]);
  29. }
  30. }];
  31. }

这里有个小 tips : 在使用 requestImageForAsset 获取图片时,如果 options 的 deliveryMode 属性使用默认值,在异步获取图片时,其回调可能会走2次。解决方案是将其显示设置为 PHImageRequestOptionsDeliveryModeHighQualityFormat 或 PHImageRequestOptionsDeliveryModeFastFormat。

  1. options.deliveryMode = PHImageRequestOptionsDeliveryModeFastFormat; // 或 PHImageRequestOptionsDeliveryModeHighQualityFormat

因为获取图片封面的回调可能会走 2 次,从而导致 dispatch_group_leave 调用次数多于 dispatch_group_enter 的调用次数,因此可能会发生 crash。

源码实现

  • dispatch_group_enter
  1. void
  2. dispatch_group_enter(dispatch_group_t dg)
  3. {
  4. uint32_t old_bits = os_atomic_sub_orig2o(dg, dg_bits,
  5. DISPATCH_GROUP_VALUE_INTERVAL, acquire);
  6. uint32_t old_value = old_bits & DISPATCH_GROUP_VALUE_MASK;
  7. if (unlikely(old_value == 0)) {
  8. _dispatch_retain(dg); // <rdar://problem/22318411>
  9. }
  10. if (unlikely(old_value == DISPATCH_GROUP_VALUE_MAX)) {
  11. DISPATCH_CLIENT_CRASH(old_bits,
  12. "Too many nested calls to dispatch_group_enter()");
  13. }
  14. }
  • dispatch_group_leave
  1. void
  2. dispatch_group_leave(dispatch_group_t dg)
  3. {
  4. uint64_t new_state, old_state = os_atomic_add_orig2o(dg, dg_state,
  5. DISPATCH_GROUP_VALUE_INTERVAL, release);
  6. uint32_t old_value = (uint32_t)(old_state &amp; DISPATCH_GROUP_VALUE_MASK);
  7. if (unlikely(old_value == DISPATCH_GROUP_VALUE_1)) {
  8. old_state += DISPATCH_GROUP_VALUE_INTERVAL;
  9. do {
  10. new_state = old_state;
  11. if ((old_state &amp; DISPATCH_GROUP_VALUE_MASK) == 0) {
  12. new_state &amp;= ~DISPATCH_GROUP_HAS_WAITERS;
  13. new_state &amp;= ~DISPATCH_GROUP_HAS_NOTIFS;
  14. } else {
  15. new_state &amp;= ~DISPATCH_GROUP_HAS_NOTIFS;
  16. }
  17. if (old_state == new_state) break;
  18. } while (unlikely(!os_atomic_cmpxchgv2o(dg, dg_state,
  19. old_state, new_state, &amp;old_state, relaxed)));
  20. return _dispatch_group_wake(dg, old_state, true);
  21. }
  22. if (unlikely(old_value == 0)) {
  23. DISPATCH_CLIENT_CRASH((uintptr_t)old_value,
  24. "Unbalanced call to dispatch_group_leave()");
  25. }
  26. }

以上就是iOS GCD之dispatch_group_enter和dispatch_group_leave使用的详细内容,更多关于iOS GCD使用的资料请关注w3xue其它相关文章!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号