輕風解語 發表於 2023-3-30 15:52:43

iOS GCD之dispatch_group_enter和dispatch_group_leave使用

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>正文</li><ul class="second_class_ul"><li>如何使用</li><li>crash 场景分析</li><li>源码实现</li></ul></ul></div><p class="maodian"></p><h2>正文</h2>
<p>在实际开发中,经常需要在几个任务全部执行完成之后,在执行后续操作,在 iOS 中,我们可以通过 NSOperation 等达到这一目的。在本篇文章中,我们会介绍如何通过 <strong>dispatch_group_enter</strong> 和 <strong>dispatch_group_leave</strong> 来实现这一功能,以及使用过程中遇到的坑。</p>
<p class="maodian"></p><h3>如何使用</h3>
<p>通过一个例子来看下如何使用 <strong>dispatch_group_enter</strong> 和 <strong>dispatch_group_leave</strong>:</p>
<div class="jb51code"><pre class="brush:cpp;">{
    // 首先 需要创建一个线程组
    dispatch_group_t group = dispatch_group_create();
    // 任务1
    dispatch_group_enter(group);
    NSURLSessionDataTask *task1 = [ dataTaskWithURL: completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
      NSLog(@"任务1完成");
      dispatch_group_leave(group);
    }];
    ;
   // 任务2
    dispatch_group_enter(group);
    NSURLSessionDataTask *task2 = [ dataTaskWithURL: completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
      NSLog(@"任务2完成");
      dispatch_group_leave(group);
    }];
    ;
    // 全部完成
    dispatch_group_notify(group, dispatch_get_main_queue(), ^(){
      NSLog(@"全部完成");
    });
}
</pre></div>
<p>任务1和任务2执行完成之后,才会执行全部完成中的任务。</p>
<p>**注意:**在使用时,dispatch_group_enter 和 dispatch_group_leave 需要成对出现,如果 dispatch_group_leave 的调用次数多于 dispatch_group_enter 的调用次数,程序会 crash。相反,虽然不会发生 crash , 但可能不会达到预期效果。</p>
<p class="maodian"></p><h3>crash 场景分析</h3>
<p>使用场景是,需要异步获取多个图片封面,所有都获取完成后,在执行指定任务,代码示例如下:</p>
<div class="jb51code"><pre class="brush:cpp;">- (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 &lt; 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 = [ init];
    if (@available(iOS 14.0, *)) {
      options.version = PHImageRequestOptionsVersionCurrent;
    }
    options.networkAccessAllowed = YES;
    [ requestImageForAsset:asset
                                             targetSize:targeSize
                                              contentMode:PHImageContentModeAspectFill
                                                options:options
                                          resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
      if (resultHandler) {
            resultHandler(result, info, [ boolValue]);
      }
    }];
}
</pre></div>
<p>这里有个小 <strong>tips</strong> : 在使用 requestImageForAsset 获取图片时,如果 options 的 deliveryMode 属性使用默认值,在异步获取图片时,其回调可能会走2次。解决方案是将其显示设置为 PHImageRequestOptionsDeliveryModeHighQualityFormat 或 PHImageRequestOptionsDeliveryModeFastFormat。</p>
<div class="jb51code"><pre class="brush:cpp;">    options.deliveryMode = PHImageRequestOptionsDeliveryModeFastFormat; // 或 PHImageRequestOptionsDeliveryModeHighQualityFormat
</pre></div>
<p>因为获取图片封面的回调可能会走 2 次,从而导致 dispatch_group_leave 调用次数多于 dispatch_group_enter 的调用次数,因此可能会发生 crash。</p>
<p class="maodian"></p><h3>源码实现</h3>
<ul><li>dispatch_group_enter</li></ul>
<div class="jb51code"><pre class="brush:cpp;">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 &amp; DISPATCH_GROUP_VALUE_MASK;
    if (unlikely(old_value == 0)) {
            _dispatch_retain(dg); // &lt;rdar://problem/22318411&gt;
    }
    if (unlikely(old_value == DISPATCH_GROUP_VALUE_MAX)) {
            DISPATCH_CLIENT_CRASH(old_bits,
                            "Too many nested calls to dispatch_group_enter()");
    }
}
</pre></div>
<ul><li>dispatch_group_leave</li></ul>
<div class="jb51code"><pre class="brush:cpp;">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 &amp;amp; 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 &amp;amp; DISPATCH_GROUP_VALUE_MASK) == 0) {
                            new_state &amp;amp;= ~DISPATCH_GROUP_HAS_WAITERS;
                            new_state &amp;amp;= ~DISPATCH_GROUP_HAS_NOTIFS;
                    } else {
                            new_state &amp;amp;= ~DISPATCH_GROUP_HAS_NOTIFS;
                    }
                    if (old_state == new_state) break;
            } while (unlikely(!os_atomic_cmpxchgv2o(dg, dg_state,
                            old_state, new_state, &amp;amp;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()");
    }
}</pre></div>
<p>以上就是iOS GCD之dispatch_group_enter和dispatch_group_leave使用的详细内容,更多关于iOS GCD使用的资料请关注琼殿技术社区其它相关文章!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>iOS定时器的选择CADisplayLink&nbsp;NSTimer和GCD使用</li><li>iOS开发探索多线程GCD常用函数</li><li>iOS开发探索多线程GCD任务示例详解</li><li>iOS开发探索多线程GCD队列示例详解</li><li>IOS开发之多线程NSThiread GCD NSOperation Runloop</li><li>iOS简单易用的GCD计时器的实现原理</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: iOS GCD之dispatch_group_enter和dispatch_group_leave使用