iOS簡單優(yōu)雅的實現(xiàn)復雜情況下的串行需求(各種鎖逝钥、GCD 、NSOperationQueue...)

iOS簡單優(yōu)雅的實現(xiàn)復雜情況下的串行需求(各種鎖拱镐、GCD 艘款、NSOperationQueue...)

昨天一個同事問我一個問題,我在開發(fā)中有很多異步操作,回調(diào)都需要時間,且時間都不確定,例如一個網(wǎng)絡請求,就是這樣的形式,異步發(fā)起請求,等待回調(diào),等到獲取結(jié)果之后進行下一步的操作.
我說,沒有任何問題啊.本來耗時操作等就是這么寫的啊...
然后他說,我現(xiàn)在有一個新的需求,例如網(wǎng)絡請求1結(jié)束后請求2等到2回來之后再請求3....層層下去...按照順序來,我說這個需求不算太難.
但是鑒于這個需求很多人都有可能會用到,于是我打算把它給寫下來分享給大家

每一次的異步操作大概可以簡化成如下:

-(void)doSomeThingForFlag:(NSInteger)flag finish:(void(^)())finshed{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"do:%ld",(long)flag);
        sleep(2+arc4random_uniform(4));
        NSLog(@"finish:%ld",(long)flag);
        if (finshed) {
            finshed();
        }
    });
}

那么正常情況下的寫法就是直接按照順序來

-(void)nomal{
    [self doSomeThingForFlag:1 finish:nil];
    
    [self doSomeThingForFlag:2 finish:nil];
    
    [self doSomeThingForFlag:3 finish:nil];
    
    [self doSomeThingForFlag:4 finish:nil];
}

那么這樣有效果么?我們運行看看...


這里寫圖片描述

很顯然,結(jié)果沒有想象的那么簡單,開始請求就已經(jīng)是無序了...

沒有辦法,那么使用嵌套?就是最普通的方式看看,

/**
 邏輯嵌套
 */
-(void)useNested{
    __weak typeof(self)weakSelf = self;
    [self doSomeThingForFlag:1 finish:^{
        
        [weakSelf doSomeThingForFlag:2 finish:^{
            
            [weakSelf doSomeThingForFlag:3 finish:^{
                
                [weakSelf doSomeThingForFlag:4 finish:nil];
            }];
        }];
    }];
}
這里寫圖片描述

OK ,結(jié)果完全按照想要的順序1->2->3->4
但是這樣寫會不會就覺得很嵌套的太多了呢?有沒有辦法不使用這種嵌套來完成這個邏輯呢?
開始構(gòu)思,首先想到的就是鎖,是的,應用開發(fā)中有很多所能夠完成
iOS作為源自C的更高級語言,自然而然也少不了有各種鎖的實現(xiàn).包含C語言的話,
有OSSpinLock、pthead沃琅、@synchronized哗咆、NSLock......大概7、8種以上吧..
在不考慮各種鎖的性能的情況下,那么是不是所有的都特別適用呢?
我一個一個舉例嘗試,大致的思路就是創(chuàng)建一個鎖,然后通過加鎖和解鎖的操作來實現(xiàn)串行的需求
首先是使用

pthread_mutex 互斥鎖

#import <pthread.h>

/**
 pthread_mutex 互斥鎖
 */
-(void)usePthred{
    static pthread_mutex_t pLock;
    pthread_mutex_init(&pLock, NULL);
    
    pthread_mutex_lock(&pLock);
    NSLog(@"1上鎖");
    [self doSomeThingForFlag:1 finish:^{
        NSLog(@"1解鎖");
        pthread_mutex_unlock(&pLock);
    }];
    
    pthread_mutex_lock(&pLock);
    NSLog(@"2上鎖");
    [self doSomeThingForFlag:2 finish:^{
        NSLog(@"2解鎖");
        pthread_mutex_unlock(&pLock);
    }];
    
    pthread_mutex_lock(&pLock);
    NSLog(@"3上鎖");
    [self doSomeThingForFlag:3 finish:^{
        NSLog(@"3解鎖");
        pthread_mutex_unlock(&pLock);
    }];
    
    pthread_mutex_lock(&pLock);
    NSLog(@"4上鎖");
    [self doSomeThingForFlag:4 finish:^{
        NSLog(@"4解鎖");
        pthread_mutex_unlock(&pLock);
    }];
}

好吧,輕易的實現(xiàn)了


這里寫圖片描述

那么既然互斥鎖可以,我再試試另一種pthead

pthread_mutex(recursive) 遞歸鎖

/**
 pthread_mutex(recursive) 遞歸鎖
 */
-(void)usePthredResursive{
    static pthread_mutex_t pLock;
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr); //初始化attr并且給它賦予默認
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //設置鎖類型益眉,這邊是設置為遞歸鎖
    pthread_mutex_init(&pLock, &attr);
    pthread_mutexattr_destroy(&attr); //銷毀一個屬性對象晌柬,在重新進行初始化之前該結(jié)構(gòu)不能重新使用
    
    static void (^RecursiveBlock)(int);
    __weak typeof(self)weakSelf = self;
    RecursiveBlock = ^(int value) {
        pthread_mutex_lock(&pLock);
        if (value>0) {
            [weakSelf doSomeThingForFlag:5-value finish:^{
                RecursiveBlock(value-1);
            }];
        }
        //        if (value == 4) {
        //            [self doSomeThingForFlag:1 finish:^{
        //                RecursiveBlock(3);
        //            }];
        //        }
        //        if (value == 3) {
        //            [self doSomeThingForFlag:2 finish:^{
        //                RecursiveBlock(2);
        //            }];
        //        }
        //        if (value == 2) {
        //            [self doSomeThingForFlag:3 finish:^{
        //                RecursiveBlock(1);
        //            }];
        //        }
        //        if (value == 1) {
        //            [self doSomeThingForFlag:4 finish:^{
        //                RecursiveBlock(0);
        //            }];
        //        }
        pthread_mutex_unlock(&pLock);
    };
    RecursiveBlock(4);
}

遞歸鎖允許同一個線程在未釋放其擁有的鎖時反復對該鎖進行加鎖操作姥份。
結(jié)果也是可以能夠?qū)崿F(xiàn)的


這里寫圖片描述

表面上看感覺遞歸鎖貌似是沒有問題的 但是其實在這里鎖并沒有起到作用,這里的鎖只是鎖住了doSomeThingForFlag:finish: 這個方法而已
其實我們把這些全部去掉看看.

-(void)usePthredResursive{
//    static pthread_mutex_t pLock;
//    pthread_mutexattr_t attr;
//    pthread_mutexattr_init(&attr); //初始化attr并且給它賦予默認
//    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //設置鎖類型,這邊是設置為遞歸鎖
//    pthread_mutex_init(&pLock, &attr);
//    pthread_mutexattr_destroy(&attr); //銷毀一個屬性對象年碘,在重新進行初始化之前該結(jié)構(gòu)不能重新使用
    
    static void (^RecursiveBlock)(int);
    __weak typeof(self)weakSelf = self;
    RecursiveBlock = ^(int value) {
//        pthread_mutex_lock(&pLock);
        if (value>0) {
            [weakSelf doSomeThingForFlag:5-value finish:^{
                RecursiveBlock(value-1);
            }];
        }
//        pthread_mutex_unlock(&pLock);
    };
    RecursiveBlock(4);
}
這里寫圖片描述

結(jié)果也是一樣?是的,沒有想的那么高深,他只是上面嵌套的另一種寫法而已,所以遞歸鎖并沒有效果,它只是鎖住方法本身,保證一次只有一個執(zhí)行而已,如果我們把block的調(diào)用放到方法的外面一樣沒有作用

-(void)usePthredResursive{
    static pthread_mutex_t pLock;
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr); //初始化attr并且給它賦予默認
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //設置鎖類型澈歉,這邊是設置為遞歸鎖
    pthread_mutex_init(&pLock, &attr);
    pthread_mutexattr_destroy(&attr); //銷毀一個屬性對象,在重新進行初始化之前該結(jié)構(gòu)不能重新使用
    
    static void (^RecursiveBlock)(int);
    __weak typeof(self)weakSelf = self;
    RecursiveBlock = ^(int value) {
        pthread_mutex_lock(&pLock);
        if (value>0) {
            [weakSelf doSomeThingForFlag:5-value finish:nil];
            RecursiveBlock(value-1);
        }
        pthread_mutex_unlock(&pLock);
    };
    RecursiveBlock(4);
}

這樣的話就是沒有達到需求


這里寫圖片描述

所以我打算放棄遞歸鎖的實現(xiàn),比如NSRecursiveLock,我直接放棄
聯(lián)想到這樣的方式,我打算再次放棄另外一種鎖@synchronized 因為它也只能鎖住方法的本身,并控制不了回調(diào)的結(jié)果

那么就么有方法了么?只能使用遞歸或者嵌套 或者互斥鎖么?

C的方法我又想到了自旋鎖

OSSpinLock 自旋鎖

#import <libkern/OSAtomic.h>

/**
 OSSpinLock 自旋鎖
 */
-(void)useOSSpinLock{
    __block OSSpinLock oslock = OS_SPINLOCK_INIT;
    
    OSSpinLockLock(&oslock);
    NSLog(@"1上鎖");
    [self doSomeThingForFlag:1 finish:^{
        NSLog(@"1解鎖");
        OSSpinLockUnlock(&oslock);
    }];
    
    OSSpinLockLock(&oslock);
    NSLog(@"2上鎖");
    [self doSomeThingForFlag:2 finish:^{
        NSLog(@"2解鎖");
        OSSpinLockUnlock(&oslock);
    }];
    
    
    OSSpinLockLock(&oslock);
    NSLog(@"3上鎖");
    [self doSomeThingForFlag:3 finish:^{
        NSLog(@"3解鎖");
        OSSpinLockUnlock(&oslock);
    }];
    
    OSSpinLockLock(&oslock);
    NSLog(@"4上鎖");
    [self doSomeThingForFlag:4 finish:^{
        NSLog(@"4解鎖");
        OSSpinLockUnlock(&oslock);
    }];
}
這里寫圖片描述

結(jié)果終于回到了想要的局面,OSSpinLock沒有讓我失望.

至此我就想到了,無上面寫的方法基本上就是使用各種鎖的實現(xiàn),來達到需求,在結(jié)果回調(diào)前把線程給鎖住,無法繼續(xù)新的線程,知道該線程的鎖解開

那么我們能不能使用多線程的某些方法來實現(xiàn)呢?比如阻塞線程,比如線程的暫停和恢復
首先想到的就是GCD
類似于OSSpinLock, 我們嘗試使用GCD的信號量看看能不能夠?qū)崿F(xiàn)

dispatch_semaphore_t

/**
 GCD single
 */
-(void)useGCDSingle{
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"1阻塞線程");
    [self doSomeThingForFlag:1 finish:^{
        NSLog(@"1釋放線程");
        dispatch_semaphore_signal(semaphore);
    }];
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"2阻塞線程");
    [self doSomeThingForFlag:2 finish:^{
        NSLog(@"2釋放線程");
        dispatch_semaphore_signal(semaphore);
    }];
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"3阻塞線程");
    [self doSomeThingForFlag:3 finish:^ {
        NSLog(@"3釋放線程");
        dispatch_semaphore_signal(semaphore);
    }];
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"4阻塞線程");
    [self doSomeThingForFlag:4 finish:^{
        NSLog(@"4釋放線程");
        dispatch_semaphore_signal(semaphore);
    }];
}
這里寫圖片描述

或者使用

dispatch_suspend屿衅、dispatch_resume

這里需要注意一些東西

  • dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //獲得程序進程缺省產(chǎn)生的并發(fā)隊列埃难,可設定優(yōu)先級來選擇高、中涤久、低三個優(yōu)先級隊列涡尘。由于是系統(tǒng)默認生成的,所以無法調(diào)用dispatch_resume()和dispatch_suspend()來控制執(zhí)行繼續(xù)或中斷响迂。
    那么想使用這個怎么辦呢?
    我們可以這樣想.因為這幾個方法調(diào)用來看,大的方式是是串行隊列,那么就創(chuàng)建一個串行隊列以供暫停和恢復就好了
/**
 GCD隊列的暫停和恢復
 */
-(void)useGCDSuspendAndResume{
    dispatch_queue_t myqueue = dispatch_queue_create("com.charles.queue", NULL);
    
    dispatch_async(myqueue, ^{
        dispatch_suspend(myqueue);
        [self doSomeThingForFlag:1 finish:^(NSInteger flag) {
            dispatch_resume(myqueue);
        }];
        
        
        dispatch_suspend(myqueue);
        [self doSomeThingForFlag:2 finish:^(NSInteger flag) {
            dispatch_resume(myqueue);
        }];
        
        
        dispatch_suspend(myqueue);
        [self doSomeThingForFlag:3 finish:^(NSInteger flag) {
            dispatch_resume(myqueue);
        }];
        
        
        dispatch_suspend(myqueue);
        [self doSomeThingForFlag:4 finish:^(NSInteger flag) {
            dispatch_resume(myqueue);
        }];
    });
}
這里寫圖片描述

無效!我是哪里想錯了么?
暫涂汲恢復,想一想,串行隊列嘛,當然要串行的添加啦,

/**
 GCD隊列的暫停和恢復
 */
-(void)useGCDSuspendAndResume{
    dispatch_queue_t myqueue = dispatch_queue_create("com.charles.queue", NULL);
    
    dispatch_async(myqueue, ^{
        dispatch_suspend(myqueue);
        [self doSomeThingForFlag:1 finish:^(NSInteger flag) {
            dispatch_resume(myqueue);
        }];
    });
    
    dispatch_async(myqueue, ^{
        dispatch_suspend(myqueue);
        [self doSomeThingForFlag:2 finish:^(NSInteger flag) {
            dispatch_resume(myqueue);
        }];
    });
    
    dispatch_async(myqueue, ^{
        dispatch_suspend(myqueue);
        [self doSomeThingForFlag:3 finish:^(NSInteger flag) {
            dispatch_resume(myqueue);
        }];
    });
    
    dispatch_async(myqueue, ^{
        dispatch_suspend(myqueue);
        [self doSomeThingForFlag:4 finish:^(NSInteger flag) {
            dispatch_resume(myqueue);
        }];
    });
}
這里寫圖片描述

果然,開始是我想錯了....

那么既然GCD可以,我使用NSOperationQueue呢?

NSOperationQueue

/**
 operationQueue的暫停和恢復
 */
-(void)useOperationQueue{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue setMaxConcurrentOperationCount:1];
    
    __weak typeof(self)weakSelf = self;
    NSBlockOperation * operation1 = [NSBlockOperation blockOperationWithBlock:^{
        [queue setSuspended:YES];
        [weakSelf doSomeThingForFlag:1 finish:^(NSInteger flag) {
            [queue setSuspended:NO];
        }];
    }];
    
    NSBlockOperation * operation2 = [NSBlockOperation blockOperationWithBlock:^{
        [queue setSuspended:YES];
        [weakSelf doSomeThingForFlag:2 finish:^(NSInteger flag) {
            [queue setSuspended:NO];
        }];
    }];
    
    NSBlockOperation * operation3 = [NSBlockOperation blockOperationWithBlock:^{
        [queue setSuspended:YES];
        [weakSelf doSomeThingForFlag:3 finish:^(NSInteger flag) {
            [queue setSuspended:NO];
        }];
    }];
    
    NSBlockOperation * operation4 = [NSBlockOperation blockOperationWithBlock:^{
        [queue setSuspended:YES];
        [weakSelf doSomeThingForFlag:4 finish:^(NSInteger flag) {
            [queue setSuspended:NO];
        }];
    }];
    
    [operation4 addDependency:operation3];
    [operation3 addDependency:operation2];
    [operation2 addDependency:operation1];
    
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];
    [queue addOperation:operation4];
}

完全參考GCD的思路,創(chuàng)建每一個操作,添加依賴和最大并發(fā)數(shù)保證大的串行,同是在沒操作其中一個暫停隊列,完成后恢復運行....


這里寫圖片描述

OK說了這么多,其實就是通過各種方式來實現(xiàn)我最上面提到的需求而已.
這樣的操作真的有用么?
很碰巧,我最近在做藍牙開發(fā),有這樣的類似需求,藍牙發(fā)送指令并接收到設備端返回數(shù)據(jù)的情況就是一次類似的網(wǎng)絡請求,
我碰到的需求是按順序的設置指令到藍牙設備端,如果是多個UUID或者characteristic的話,之間不沖突,沒有影響,但是可惜的是我要操作的是一個characteristic,我只能這么做,因為如果同一時間發(fā)送指令不是按照上面的邏輯的話,就會造成丟包.我可能發(fā)送了某一個指令,但是藍牙沒有收到或者未處理就來了新的指令導致我無法完整的操作它.我必須保證1->2->3->4的邏輯順序,

我很高興我正好在研究這個,所以我能夠即時的給到我同事我的思路,,并且今天把它分享給你們
附上demo的地址
https://github.com/spicyShrimp/specialSync.git

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市栓拜,隨后出現(xiàn)的幾起案子座泳,更是在濱河造成了極大的恐慌,老刑警劉巖幕与,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挑势,死亡現(xiàn)場離奇詭異,居然都是意外死亡啦鸣,警方通過查閱死者的電腦和手機潮饱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來诫给,“玉大人香拉,你說我怎么就攤上這事≈锌瘢” “怎么了凫碌?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長胃榕。 經(jīng)常有香客問我盛险,道長,這世上最難降的妖魔是什么勋又? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任苦掘,我火速辦了婚禮,結(jié)果婚禮上楔壤,老公的妹妹穿的比我還像新娘鹤啡。我一直安慰自己,他們只是感情好蹲嚣,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布递瑰。 她就那樣靜靜地躺著祟牲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪泣矛。 梳的紋絲不亂的頭發(fā)上疲眷,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機與錄音您朽,去河邊找鬼。 笑死换淆,一個胖子當著我的面吹牛哗总,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播倍试,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼讯屈,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了县习?” 一聲冷哼從身側(cè)響起涮母,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎躁愿,沒想到半個月后叛本,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡彤钟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年来候,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逸雹。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡营搅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出梆砸,到底是詐尸還是另有隱情转质,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布帖世,位于F島的核電站休蟹,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏狮暑。R本人自食惡果不足惜鸡挠,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望搬男。 院中可真熱鬧拣展,春花似錦、人聲如沸缔逛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至按脚,卻和暖如春于毙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背辅搬。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工唯沮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人堪遂。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓介蛉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親溶褪。 傳聞我的和親對象是個殘疾皇子币旧,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

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

  • 原文地址 http://www.cnblogs.com/kenshincui/p/3983982.html 大家都...
    怎樣m閱讀 1,264評論 0 1
  • 多線程 在iOS開發(fā)中為提高程序的運行效率會將比較耗時的操作放在子線程中執(zhí)行,iOS系統(tǒng)進程默認啟動一個主線程猿妈,用...
    郭豪豪閱讀 2,587評論 0 4
  • 概覽 1.多線程 1.1 簡介 1.2 iOS 多線程 2.NSThread 2.1 解決多線程阻塞問題 2.2 ...
    Yiart閱讀 2,137評論 0 8
  • 從哪說起呢吹菱? 單純講多線程編程真的不知道從哪下嘴。彭则。 不如我直接引用一個最簡單的問題鳍刷,以這個作為切入點好了 在ma...
    Mr_Baymax閱讀 2,734評論 1 17
  • 我突然想起, 你已沉睡在六月的寒冬里贰剥, 穆肅莊重倾剿, 臉頰上沒有一絲微笑, 也沒有一絲悲傷蚌成。 仿佛我能夠吻你前痘, 把你...
    希爾大閱讀 233評論 0 3