iOS?GCD之dispatch_group_enter和dispatch_group_leave使用
正文
在實(shí)際開(kāi)發(fā)中,經(jīng)常需要在幾個(gè)任務(wù)全部執(zhí)行完成之后,在執(zhí)行后續(xù)操作,在 iOS 中,我們可以通過(guò) NSOperation 等達(dá)到這一目的。在本篇文章中,我們會(huì)介紹如何通過(guò) dispatch_group_enter 和 dispatch_group_leave 來(lái)實(shí)現(xiàn)這一功能,以及使用過(guò)程中遇到的坑。
如何使用
通過(guò)一個(gè)例子來(lái)看下如何使用 dispatch_group_enter 和 dispatch_group_leave:
{
// 首先 需要?jiǎng)?chuàng)建一個(gè)線程組
dispatch_group_t group = dispatch_group_create();
// 任務(wù)1
dispatch_group_enter(group);
NSURLSessionDataTask *task1 = [[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@""] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"任務(wù)1完成");
dispatch_group_leave(group);
}];
[task1 resume];
// 任務(wù)2
dispatch_group_enter(group);
NSURLSessionDataTask *task2 = [[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@""] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"任務(wù)2完成");
dispatch_group_leave(group);
}];
[task2 resume];
// 全部完成
dispatch_group_notify(group, dispatch_get_main_queue(), ^(){
NSLog(@"全部完成");
});
}
任務(wù)1和任務(wù)2執(zhí)行完成之后,才會(huì)執(zhí)行全部完成中的任務(wù)。
**注意:**在使用時(shí),dispatch_group_enter 和 dispatch_group_leave 需要成對(duì)出現(xiàn),如果 dispatch_group_leave 的調(diào)用次數(shù)多于 dispatch_group_enter 的調(diào)用次數(shù),程序會(huì) crash。相反,雖然不會(huì)發(fā)生 crash , 但可能不會(huì)達(dá)到預(yù)期效果。
crash 場(chǎng)景分析
使用場(chǎng)景是,需要異步獲取多個(gè)圖片封面,所有都獲取完成后,在執(zhí)行指定任務(wù),代碼示例如下:
- (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(), ^(){
// 全部完成后 執(zhí)行指定任務(wù)
});
}
- (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]);
}
}];
}
這里有個(gè)小 tips : 在使用 requestImageForAsset 獲取圖片時(shí),如果 options 的 deliveryMode 屬性使用默認(rèn)值,在異步獲取圖片時(shí),其回調(diào)可能會(huì)走2次。解決方案是將其顯示設(shè)置為 PHImageRequestOptionsDeliveryModeHighQualityFormat 或 PHImageRequestOptionsDeliveryModeFastFormat。
options.deliveryMode = PHImageRequestOptionsDeliveryModeFastFormat; // 或 PHImageRequestOptionsDeliveryModeHighQualityFormat
因?yàn)楂@取圖片封面的回調(diào)可能會(huì)走 2 次,從而導(dǎo)致 dispatch_group_leave 調(diào)用次數(shù)多于 dispatch_group_enter 的調(diào)用次數(shù),因此可能會(huì)發(fā)生 crash。
源碼實(shí)現(xiàn)
- dispatch_group_enter
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()");
}
}
- dispatch_group_leave
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使用的詳細(xì)內(nèi)容,更多關(guān)于iOS GCD使用的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
iOS開(kāi)發(fā)之MRC(手動(dòng)內(nèi)存管理)詳解
這篇文章主要介紹了?iOS開(kāi)發(fā)之MRC(手動(dòng)內(nèi)存管理)詳解的相關(guān)資料,需要的朋友可以參考下2022-08-08
iOS實(shí)現(xiàn)頂部標(biāo)簽式導(dǎo)航欄及下拉分類菜單
這篇文章主要為大家詳細(xì)介紹了iOS實(shí)現(xiàn)頂部標(biāo)簽式導(dǎo)航欄及下拉分類菜單的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-03-03
ios 不支持 iframe 的完美解決方法(兼容iOS&安卓)
下面小編就為大家?guī)?lái)一篇ios 不支持 iframe 的完美解決方法(兼容iOS&安卓)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-07-07
iOS實(shí)現(xiàn)逐幀動(dòng)畫(huà)做loading視圖
這篇文章主要為大家詳細(xì)介紹了iOS實(shí)現(xiàn)逐幀動(dòng)畫(huà)做loading視圖,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-05-05
IOS HTTP請(qǐng)求的常見(jiàn)狀態(tài)碼總結(jié)
這篇文章主要介紹了IOS HTTP請(qǐng)求的常見(jiàn)狀態(tài)碼總結(jié)的相關(guān)資料,需要的朋友可以參考下2017-02-02
iOS push側(cè)滑返回功能實(shí)現(xiàn)方法
這篇文章主要為大家詳細(xì)介紹了iOS push側(cè)滑返回功能實(shí)現(xiàn)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05
iOS瀑布流的簡(jiǎn)單實(shí)現(xiàn)(Swift)
這篇文章主要介紹了iOS瀑布流的簡(jiǎn)單實(shí)現(xiàn),說(shuō)到瀑布流, 或許大家都不陌生, 瀑布流的實(shí)現(xiàn)也有很多種! 本文使用兩種方法介紹,有興趣的可以了解一下。2016-11-11
iOS開(kāi)發(fā)之1行代碼實(shí)現(xiàn)緩存計(jì)算及清除緩存
這篇文章主要給大家介紹了關(guān)于iOS開(kāi)發(fā)之1行代碼實(shí)現(xiàn)緩存計(jì)算及清除緩存的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)各位iOS開(kāi)發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05

