#iOS簡單優雅的實現複雜狀況下的串行需求(各類鎖、GCD 、NSOperationQueue...)git
昨天一個同事問我一個問題,我在開發中有不少異步操做,回調都須要時間,且時間都不肯定,例如一個網絡請求,就是這樣的形式,異步發起請求,等待回調,等到獲取結果以後進行下一步的操做. 我說,沒有任何問題啊.原本耗時操做等就是這麼寫的啊... 而後他說,我如今有一個新的需求,例如網絡請求1結束後請求2等到2回來以後再請求3....層層下去...按照順序來,我說這個需求不算太難. 可是鑑於這個需求不少人都有可能會用到,因而我打算把它給寫下來分享給你們github
每一次的異步操做大概能夠簡化成以下:bash
-(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];
}
複製代碼
那麼這樣有效果麼?咱們運行看看... 多線程
沒有辦法,那麼使用嵌套?就是最普通的方式看看,併發
/**
邏輯嵌套
*/
-(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 ,結果徹底按照想要的順序1->2->3->4 可是這樣寫會不會就以爲很嵌套的太多了呢?有沒有辦法不使用這種嵌套來完成這個邏輯呢? 開始構思,首先想到的就是鎖,是的,應用開發中有不少所可以完成 iOS做爲源自C的更高級語言,天然而然也少不了有各類鎖的實現.包含C語言的話, 有OSSpinLock、pthead、@synchronized、NSLock......大概七、8種以上吧.. 在不考慮各類鎖的性能的狀況下,那麼是否是全部的都特別適用呢? 我一個一個舉例嘗試,大體的思路就是建立一個鎖,而後經過加鎖和解鎖的操做來實現串行的需求 首先是使用dom
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);
}];
}
複製代碼
好吧,輕易的實現了 async
那麼既然互斥鎖能夠,我再試試另外一種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); //銷燬一個屬性對象,在從新進行初始化以前該結構不能從新使用
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);
}
複製代碼
遞歸鎖容許同一個線程在未釋放其擁有的鎖時反覆對該鎖進行加鎖操做。 結果也是能夠可以實現的
表面上看感受遞歸鎖貌似是沒有問題的 可是其實在這裏鎖並無起到做用,這裏的鎖只是鎖住了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); //銷燬一個屬性對象,在從新進行初始化以前該結構不能從新使用
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);
}
複製代碼
結果也是同樣?是的,沒有想的那麼高深,他只是上面嵌套的另外一種寫法而已,因此遞歸鎖並無效果,它只是鎖住方法自己,保證一次只有一個執行而已,若是咱們把block的調用放到方法的外面同樣沒有做用
-(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); //銷燬一個屬性對象,在從新進行初始化以前該結構不能從新使用
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);
}
複製代碼
這樣的話就是沒有達到需求
因此我打算放棄遞歸鎖的實現,好比NSRecursiveLock,我直接放棄 聯想到這樣的方式,我打算再次放棄另一種鎖@synchronized 由於它也只能鎖住方法的自己,並控制不了回調的結果
那麼就麼有方法了麼?只能使用遞歸或者嵌套 或者互斥鎖麼?
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);
}];
}
複製代碼
結果終於回到了想要的局面,OSSpinLock沒有讓我失望.
至此我就想到了,無上面寫的方法基本上就是使用各類鎖的實現,來達到需求,在結果回調前把線程給鎖住,沒法繼續新的線程,知道該線程的鎖解開
那麼咱們能不能使用多線程的某些方法來實現呢?好比阻塞線程,好比線程的暫停和恢復 首先想到的就是GCD 相似於OSSpinLock, 咱們嘗試使用GCD的信號量看看能不可以實現
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
這裏須要注意一些東西
/**
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的思路,建立每個操做,添加依賴和最大併發數保證大的串行,同是在沒操做其中一個暫停隊列,完成後恢復運行....
OK說了這麼多,其實就是經過各類方式來實現我最上面提到的需求而已. 這樣的操做真的有用麼? 很碰巧,我最近在作藍牙開發,有這樣的相似需求,藍牙發送指令並接收到設備端返回數據的狀況就是一次相似的網絡請求, 我碰到的需求是按順序的設置指令到藍牙設備端,若是是多個UUID或者characteristic的話,之間不衝突,沒有影響,可是惋惜的是我要操做的是一個characteristic,我只能這麼作,由於若是同一時間發送指令不是按照上面的邏輯的話,就會形成丟包.我可能發送了某一個指令,可是藍牙沒有收到或者未處理就來了新的指令致使我沒法完整的操做它.我必須保證1->2->3->4的邏輯順序,
我很高興我正好在研究這個,因此我可以即時的給到我同事個人思路,,而且今天把它分享給大家 附上demo的地址 github.com/spicyShrimp…
還有我寫的系列文章,剛剛開始寫,但願多關注一下. 系列:iOS開發-前言+大綱 blog.csdn.net/spicyShrimp…