iOS 等待for循環(huán)里的異步任務完成再進行其他操作的一個解決辦法 -- 信號量+串行隊列卡for小循環(huán)

for循環(huán)里的異步操作

開發(fā)中經(jīng)常會遇到這樣一些情況,比如:
1.登錄失敗后的多次登錄重連場景仍翰。
2.在一個for循環(huán)遍歷里赫粥,有多種異步操作,需要在所有的異步操作完成后歉备,也就是for循環(huán)的遍歷結束后傅是,再去執(zhí)行其他操作,但是不能卡主線程蕾羊,這時候就需要用其他方法了喧笔。

我遇到的需求是,在一個for循環(huán)里有數(shù)據(jù)庫的查詢操作以及網(wǎng)絡請求操作龟再,然后將數(shù)據(jù)庫的查詢和網(wǎng)絡請求的結果添加到一個臨時數(shù)組中书闸,最后等for循環(huán)結束后,將臨時數(shù)組通過block回傳出去利凑。
解決辦法如下:

串行隊列配合信號量

因為for循環(huán)不能卡主線程浆劲,所以我們首先要創(chuàng)建一個串行的隊列(并發(fā)的不可以),然后通過信號量來控制for循環(huán)哀澈,下面是模擬的一個操作

printf("處理前創(chuàng)建信號量牌借,開始循環(huán)異步請求操作\n");

// 1.創(chuàng)建一個串行隊列,保證for循環(huán)依次執(zhí)行
        dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);

// 2.異步執(zhí)行任務
dispatch_async(serialQueue, ^{
    // 3.創(chuàng)建一個數(shù)目為1的信號量割按,用于“卡”for循環(huán)膨报,等上次循環(huán)結束在執(zhí)行下一次的for循環(huán)
    dispatch_semaphore_t sema = dispatch_semaphore_create(0);
   
    for (int i = 0; i<5; i++) {
        // 開始執(zhí)行for循環(huán),讓信號量-1适荣,這樣下次操作須等信號量>=0才會繼續(xù),否則下次操作將永久停止
    
        printf("信號量等待中\(zhòng)n");
        // 模擬一個異步任務
        NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://github.com"]];
        NSURLSession *session = [NSURLSession sharedSession];
        NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        // 本次for循環(huán)的異步任務執(zhí)行完畢现柠,這時候要發(fā)一個信號,若不發(fā)弛矛,下次操作將永遠不會觸發(fā)
            [tampArray addObject:@(i)];
            NSLog(@"本次耗時操作完成够吩,信號量+1 %@\n",[NSThread currentThread]);
            dispatch_semaphore_signal(sema);
            
        }];
        [dataTask resume];
        dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    }
    
    NSLog(@"其他操作");
    for (NSNumber *num in tampArray) {
        NSLog(@"所有操作完成后的操作--->   %@\n",num);
    }
    
});

實際場景中的應用

1.從相冊里取若干原圖,先獲取到縮略圖的數(shù)組丈氓,后根據(jù)縮略圖信息取原圖周循,這是一個可同步可異步的操作,鑒于卡頓考慮万俗,采用異步取原圖的操作

 NSMutableArray *tmpPhotoes = [NSMutableArray array];
            // 這里用信號量卡 for 循環(huán) 來獲取原圖鱼鼓,等全部獲取完成再刷新UI
            dispatch_queue_t serialQueue = dispatch_queue_create("getOriginalQueue", DISPATCH_QUEUE_SERIAL);
            
            dispatch_async(serialQueue, ^{
                
                dispatch_semaphore_t sema = dispatch_semaphore_create(0);
                
                [assets enumerateObjectsUsingBlock:^(PHAsset *tmpAsset, NSUInteger idx, BOOL * _Nonnull stop) {
                    
                    /* 這里換成系統(tǒng)的方法也是一樣的,[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeAspectFit options:option resultHandler:^(UIImage *result, NSDictionary *info) {
    }];*/ 
                     __block int time = 0;  // 由于獲取原圖會有2次回調该编,PHImageResultIsDegradedKey為0時才為原圖
                    [[TZImageManager manager] getOriginalPhotoWithAsset:tmpAsset newCompletion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) {
                        // 原圖
                        if ([[info jk_stringForKey:PHImageResultIsDegradedKey] isEqualToString:@"0"]) {
                            [tmpPhotoes addObject:photo];
                        }
                        time++;
                        if (time == 2) {
                            dispatch_semaphore_signal(sema);
                        }
                    }];
                    
                    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
                    
                }];

                dispatch_async(dispatch_get_main_queue(), ^{
                    if (tmpPhotoes.count) {
                        [self refreshImageDataWithPhotos:tmpPhotoes assets:assets];
                    }
                });
            });

2.外界多個渠道隨時可能來查詢一個會話列表數(shù)組的數(shù)據(jù)迄本,但是原始的數(shù)組里沒有包含想要的東西,需要包裝一下再供外界使用课竣。首先需要創(chuàng)建一個條件鎖嘉赎,防止這個列表數(shù)據(jù)被多方訪問造成資源搶奪置媳,其次循環(huán)遍歷原始數(shù)組,通過數(shù)據(jù)庫查詢和網(wǎng)絡請求操作重新包裝會話列表里的數(shù)據(jù)公条,最后等待for循環(huán)結束拇囊,將包裝過的數(shù)組回傳給外界以供使用。

/** 會話列表 */
-(void)getConversationList:(void(^)(NSArray *conversationList))handler {
// 條件鎖靶橱,若當前資源可獲取則進行下一步寥袭,否則等待條件鎖完成才能訪問
if ([self.lock obtain]) {
    BBLog(@"BigBang- 獲取資源中");
    // 會話列表
    NSArray *oriList = [[BBConversationManager manager] getConversationList];
    
    dispatch_queue_t serailQueue = dispatch_queue_create("conversationQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(serailQueue, ^{
        
        // 每次重置,防止 dispose 
        self.sema = nil;
        self.sema = dispatch_semaphore_create(1);
        BBLog(@"BigBang- CrashInfo -- 信號量創(chuàng)建 當前線程---%@",[NSThread currentThread]);
        // 臨時數(shù)組 存儲變換后的會話模型
        __block NSMutableArray *temList = [NSMutableArray array];
        if (oriList.count > 0) {
            [oriList enumerateObjectsUsingBlock:^(RCConversation *conversation, NSUInteger idx, BOOL * _Nonnull stop) {
                
                NSString *identifier = conversation.targetId;
                RCMessage *message = [[RCIMClient sharedRCIMClient] getMessage:conversation.lastestMessageId];
                
                if ([conversation conversationType] == ConversationType_PRIVATE || [conversation conversationType] == ConversationType_SYSTEM) {
                    
                    BBLog(@"BigBang- CrashInfo 信號量-1");
                    dispatch_semaphore_wait(self.sema, DISPATCH_TIME_FOREVER);
                    
                    // 本地存在关霸,直接從本地數(shù)據(jù)庫取 這里是一個數(shù)據(jù)庫的查詢操作传黄,是在子線程中進行的
                    if ([BBUserTable isUserExist:identifier]) {
                        
                        BBConversationListModel *model = [[BBConversationListModel alloc] initWithRCMessage:message userInfo:[BBUserTable gerUserWithID:identifier]];
                        model.unreadMessageCount = conversation.unreadMessageCount;
                        if ([conversation conversationType] == ConversationType_SYSTEM) {
                            [[BBConversationManager manager] setConversationToTop:ConversationType_SYSTEM targetId:identifier isTop:YES];
                        }
                        
                        [temList addObject:model];
                        // 從數(shù)據(jù)庫獲取完成 發(fā)送信號 繼續(xù)下一次取值
                        dispatch_semaphore_signal(self.sema);
                        BBLog(@"BigBang- CrashInfo 數(shù)據(jù)庫 信號量+1");
                        
                    }else {
                        
                        //  本地不存在 調接口請求數(shù)據(jù) 這里是一個網(wǎng)絡請求操作,無論請求成功與否队寇,都要將信號量恢復
                        [[BBDataManager manager] asyncGetUserInfoWithUserID:identifier result:^(NSDictionary *userInfo) {
                            
                            BBUserInfo *newUserInfo = [[BBUserInfo alloc] initWithUserID:identifier userName:[NSString stringWithFormat:@"%@",userInfo[@"nickname"]] avatar:[NSString stringWithFormat:@"%@",userInfo[@"avatar"]] level:[NSString stringWithFormat:@"%@",userInfo[@"level"]]];
                            BBConversationListModel *model = [[BBConversationListModel alloc] initWithRCMessage:message userInfo:newUserInfo];
                            model.unreadMessageCount = conversation.unreadMessageCount;
                            
                            if ([conversation conversationType] == ConversationType_SYSTEM) {
                                [[BBConversationManager manager] setConversationToTop:ConversationType_SYSTEM targetId:identifier isTop:YES];
                            }
                            
                            [temList addObject:model];
                            
                            BBLog(@"BigBang- CrashInfo 網(wǎng)絡請求 信號量+1");
                            dispatch_semaphore_signal(self.sema);
                            [BBUserTable saveUserInfoWithModel:newUserInfo];
                            
                        } error:^(NSError *error){
                            
                            BBLog(@"BigBang- CrashInfo 網(wǎng)絡請求失敗 信號量+1");
                            dispatch_semaphore_signal(self.sema);
                            
                        }];
                    }
                }
            }];
            // 結束操作后膘掰,將鎖打開,以供后面的訪問
            if (handler) {
                if (temList.count) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        handler(temList);
                    });
                    BBLog(@"BigBang- CrashInfo 獲取數(shù)據(jù)完畢佳遣,回傳顯示  當前線程--%@ ",[NSThread currentThread]);
                }
            }
            [self.lock finish];
        }else {
            if (handler) {
                handler(nil);
            }
            [self.lock finish];
        }
    });
}else {
    BBLog(@"BigBang- 搶奪資源中识埋,等待當前獲取資源結束繼續(xù)進行");
  }
}

上面的條件鎖的文件如下: .h 文件 這里的 < > 不能用,請自行替換 ‘ 為 < >
使用方法為:

self.lock = [[BBResourceLock alloc] init];
if ([self.lock obtain]) {
// 各種任務
…

// 結束后將鎖關閉
[self.lock finish];

}
具體的文件如下:
import ‘Foundation/Foundation.h’
@interface BBResourceLock : NSObject
/**
獲取資源零渐,如果資源已經(jīng)被獲取過則返回 NO窒舟,否則返回 YES
@return 如果資源已經(jīng)被獲取過則返回 NO,否則返回 YES
*/
-(BOOL)obtain;
/**
等待資源可用
如果資源沒有被獲取過 則獲取資源并返回YES
如果資源被其他線程獲取則等待诵盼,直到資源可用并返回 YES
如果資源是被同一線程獲取則不會等待惠豺,并且返回NO
@return YES/NO
*/
-(BOOL)wait;
/**
釋放占用的資源
*/
-(void)finish;

@end

.m文件
#import “BBResourceLock.h”

@interface BBResourceLock ()
/** 當前線程 /
@property (nonatomic,strong) NSThread mOccupiedThread;
/ 條件 */
@property (nonatomic,strong) NSCondition *mCondition;
@end

@implementation BBResourceLock

(instancetype)init
{
self = [super init];
if (self) {
self.mCondition = [[NSCondition alloc] init];
}
return self;
}
/**

獲取資源,如果資源已經(jīng)被獲取過則返回 NO拦耐,否則返回 YES

@return 如果資源已經(jīng)被獲取過則返回 NO耕腾,否則返回 YES
*/
-(BOOL)obtain {
@synchronized(self.mCondition) {
if (self.mOccupiedThread) {
return NO;
}else {
self.mOccupiedThread = [NSThread currentThread];
return YES;
}
}
}

/**
等待資源可用
如果資源沒有被獲取過 則獲取資源并返回YES
如果資源被其他線程獲取則等待见剩,直到資源可用并返回 YES
如果資源是被同一線程獲取則不會等待杀糯,并且返回NO
@return YES/NO
*/
-(BOOL)wait {
@synchronized (self.mCondition) {
if (!self.mOccupiedThread) {
return [self obtain];
}
if (self.mOccupiedThread == [NSThread currentThread]) {
return YES;
}
[self.mCondition wait];
return [self obtain];
}
}

/**
釋放占用的資源
*/
-(void)finish {
@synchronized(self.mCondition) {
self.mOccupiedThread = nil;
[self.mCondition signal];
}
}

@end

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市苍苞,隨后出現(xiàn)的幾起案子固翰,更是在濱河造成了極大的恐慌,老刑警劉巖羹呵,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件骂际,死亡現(xiàn)場離奇詭異,居然都是意外死亡冈欢,警方通過查閱死者的電腦和手機歉铝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來凑耻,“玉大人太示,你說我怎么就攤上這事柠贤。” “怎么了类缤?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵臼勉,是天一觀的道長。 經(jīng)常有香客問我餐弱,道長宴霸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任膏蚓,我火速辦了婚禮瓢谢,結果婚禮上,老公的妹妹穿的比我還像新娘降允。我一直安慰自己恩闻,他們只是感情好,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布剧董。 她就那樣靜靜地躺著幢尚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪翅楼。 梳的紋絲不亂的頭發(fā)上尉剩,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天,我揣著相機與錄音毅臊,去河邊找鬼理茎。 笑死,一個胖子當著我的面吹牛管嬉,可吹牛的內(nèi)容都是我干的皂林。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蚯撩,長吁一口氣:“原來是場噩夢啊……” “哼础倍!你這毒婦竟也來了?” 一聲冷哼從身側響起胎挎,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤沟启,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后犹菇,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體德迹,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年揭芍,在試婚紗的時候發(fā)現(xiàn)自己被綠了胳搞。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖肌毅,靈堂內(nèi)的尸體忽然破棺而出币厕,到底是詐尸還是另有隱情,我是刑警寧澤芽腾,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布旦装,位于F島的核電站,受9級特大地震影響摊滔,放射性物質發(fā)生泄漏阴绢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一艰躺、第九天 我趴在偏房一處隱蔽的房頂上張望呻袭。 院中可真熱鬧,春花似錦腺兴、人聲如沸左电。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽篓足。三九已至,卻和暖如春闰蚕,著一層夾襖步出監(jiān)牢的瞬間栈拖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工没陡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留涩哟,地道東北人。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓盼玄,卻偏偏與公主長得像贴彼,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子埃儿,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容