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