- pthread
- NSThread
- GCD
1. 同步、異步、并發(fā)、串行講解
2. 創(chuàng)建隊(duì)列的幾種方式
3. 柵欄函數(shù)
4. 隊(duì)列組
5. GCD快速迭代 - NSOperation和NSOperationQueue
1. NSInvocationOperation和NSBlockOperation
2. NSOperationQueue
3. 任務(wù)依賴 - GCD和NSOperation的比較
- 多線程的安全隱患
關(guān)于多線程,在 iOS 中目前有 4 套方案,他們分別是:

下面我們分別來為大家一一介紹上述方案:
方案一:pthread
#import <pthread.h>
//創(chuàng)建線程對象
pthread_t thread = NULL;
//傳遞的參數(shù)
id str = @"i'm pthread param";
//創(chuàng)建線程
/* 參數(shù)一:線程對象 傳遞線程對象的地址
參數(shù)二:線程屬性 包括線程的優(yōu)先級等
參數(shù)三:子線程需要執(zhí)行的方法
參數(shù)四:需要傳遞的參數(shù)
*/
int result = pthread_create(&thread, NULL, operate, (__bridge void *)(str));
if (result == 0) {
NSLog(@"創(chuàng)建線程 OK");
} else {
NSLog(@"創(chuàng)建線程失敗 %d", result);
}
//手動把當(dāng)前線程結(jié)束掉
// pthread_detach:設(shè)置子線程的狀態(tài)設(shè)置為detached,則該線程運(yùn)行結(jié)束后會自動釋放所有資源。
pthread_detach(thread);
void *operate(void *params){
NSString *str = (__bridge NSString *)(params);
NSLog(@"%@ - %@", [NSThread currentThread], str);
return NULL;
}
方案二:NSThread
- 先創(chuàng)建線程類,再啟動
// 創(chuàng)建
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];
// 啟動
[thread start];
- 創(chuàng)建后立即啟動
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
方案三:GCD
它是蘋果為多核的并行運(yùn)算提出的解決方案,所以它會自動合理地利用更多的CPU內(nèi)核,最重要的是它會自動管理線程的生命周期(比如創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程)
1. 同步、異步、并發(fā)、串行講解
GCD中有2個(gè)用來執(zhí)行任務(wù)的函數(shù)
用同步的方式執(zhí)行任務(wù)
//queue:隊(duì)列 block:任務(wù)
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
用異步的方式執(zhí)行任務(wù)
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
容易混淆的術(shù)語
有4個(gè)術(shù)語比較容易混淆:同步、異步、并發(fā)、串行
同步和異步主要影響:能不能開啟新的線程
同步:在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力
異步:在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力(但是不一定能夠開啟新線程,比如異步在主隊(duì)列中執(zhí)行任務(wù))
并發(fā)和串行主要影響:任務(wù)的執(zhí)行方式
并發(fā):多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行
串行:一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù)
各種隊(duì)列的執(zhí)行效果

注意:使用sync函數(shù)往當(dāng)前串行隊(duì)列中添加任務(wù),會卡住當(dāng)前的串行隊(duì)列(產(chǎn)生死鎖)
2. 創(chuàng)建隊(duì)列的幾種方式
-
主隊(duì)列: 它是一個(gè)特殊的
串行隊(duì)列, 任何需要刷新 UI 的工作都要在主隊(duì)列執(zhí)行。
dispatch_queue_t queue = dispatch_get_main_queue();
-
自定義隊(duì)列: 自己可以創(chuàng)建
串行隊(duì)列, 也可以創(chuàng)建并行隊(duì)列。
//串行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("test1", NULL);
dispatch_queue_t queue = dispatch_queue_create("test2", DISPATCH_QUEUE_SERIAL);
//并行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("test3", DISPATCH_QUEUE_CONCURRENT);
- 全局并行隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
3. 驗(yàn)證一下所學(xué)知識
下面我們來看看下面幾段代碼,猜一猜運(yùn)行之后結(jié)果是個(gè)啥子嘛。。。
考題一:
NSLog(@"任務(wù)一");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"任務(wù)二");
});
NSLog(@"任務(wù)三");

額。。。知道結(jié)果了么?下面我們來揭曉答案
執(zhí)行結(jié)果:

為什么會這樣呢?
因?yàn)橥饺蝿?wù)會阻塞當(dāng)前線程,然后把 Block 中的任務(wù)放到主隊(duì)列中執(zhí)行,隊(duì)列是FIFO,所以Block中的任務(wù)只有等到dispatch_sync執(zhí)行完畢后才會執(zhí)行,但是dispatch_sync要想執(zhí)行完成必須Block中的任務(wù)執(zhí)行完畢后才會結(jié)束.這就是非常經(jīng)典的死鎖現(xiàn)象.
考題二:
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
NSLog(@"任務(wù)一");
dispatch_async(queue, ^{
NSLog(@"任務(wù)二");
dispatch_sync(queue, ^{
NSLog(@"任務(wù)三");
});
NSLog(@"任務(wù)四");
});
NSLog(@"任務(wù)五");
看了考題一的分析 我相信考題二難不住你的,我們來看看打印結(jié)果:

其實(shí)原因跟上一個(gè)例子我們分析的原因類似,記住這句結(jié)論就好:
注意:使用sync函數(shù)往當(dāng)前串行隊(duì)列中添加任務(wù),會卡住當(dāng)前的串行隊(duì)列(產(chǎn)生死鎖)
3. 柵欄函數(shù)
在項(xiàng)目中有很多場景需要控制任務(wù)的執(zhí)行順序,比如需要等任務(wù)A, 任務(wù)B, 任務(wù)C都完成后(其中A, B, C沒有順序要求), 才進(jìn)行下一步的處理任務(wù), 可以使用 dispatch_group很方便的完成 (也可以使用柵欄函數(shù))
如果上面的A, B, C任務(wù)順序也有順序要求呢? 必須A任務(wù)完成后, 才能進(jìn)行B任務(wù), B完成后才進(jìn)行C任務(wù), 這時(shí)我們就需要用到柵欄函數(shù)
dispatch_barrier_async:在進(jìn)程管理中起到一個(gè)柵欄的作用,該函數(shù)需要同dispatch_queue_create函數(shù)生成的并發(fā)隊(duì)列一起使用才能生效。
第一種情況
A, B, C任務(wù)完成之后(A, B, C無順序要求), 進(jìn)行任務(wù)D
1.使用dispatch_barrier
dispatch_queue_t queue = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"開始任務(wù)A");
[NSThread sleepForTimeInterval:1];
NSLog(@"任務(wù)A done.");
});
dispatch_async(queue, ^{
NSLog(@"開始任務(wù)B");
[NSThread sleepForTimeInterval:0.5];
NSLog(@"任務(wù)B done.");
});
dispatch_async(queue, ^{
NSLog(@"開始任務(wù)C");
[NSThread sleepForTimeInterval:0.2];
NSLog(@"任務(wù)C done.");
});
dispatch_barrier_async(queue, ^{
NSLog(@"----------> barrier <----------");
});
dispatch_async(queue, ^{
NSLog(@"開始任務(wù)D");
});
打印結(jié)果:
開始任務(wù)C
開始任務(wù)A
開始任務(wù)B
任務(wù)C done.
任務(wù)B done.
任務(wù)A done.
----------> barrier <----------
開始任務(wù)D
可以看出在執(zhí)行完柵欄前面的操作之后才執(zhí)行柵欄操作,然后再執(zhí)行柵欄后邊的操作。
如果不加barrier 函數(shù), 輸出如下:
開始任務(wù)B
開始任務(wù)A
開始任務(wù)D
開始任務(wù)C
任務(wù)C done.
任務(wù)B done.
任務(wù)A done.
dispatch_barrier_async和dispatch_barrier_sync使用區(qū)別:
dispatch_barrier_async和dispatch_barrier_sync是 GCD 中的兩個(gè)方法。是不是和dispatch_async及dispatch_sync長得很像,就是多了一個(gè)barrier(譯:柵欄)。
沒錯(cuò),除了有dispatch_async或dispatch_sync的作用外(是否阻塞當(dāng)前線程),還有“柵欄”的效果。
意思就是,在該隊(duì)列,以他們?yōu)榻纾懊嫒蝿?wù)執(zhí)行完成,再把自己內(nèi)部的任務(wù)執(zhí)行完,才會執(zhí)行后面的任務(wù)。
知道和dispatch_async及dispatch_sync對應(yīng),就應(yīng)該想到:
dispatch_barrier_async不阻塞當(dāng)前線程,dispatch_barrier_async里面的任務(wù)異步執(zhí)行。
dispatch_barrier_sync會阻塞當(dāng)前線程,dispatch_barrier_sync里面的任務(wù)同步執(zhí)行。
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQ", DISPATCH_QUEUE_CONCURRENT);
//以下任務(wù)
dispatch_async(concurrentQueue, ^{ NSLog(@"任務(wù)1"); });
dispatch_async(concurrentQueue, ^{ NSLog(@"任務(wù)2"); });
dispatch_async(concurrentQueue, ^{ NSLog(@"任務(wù)3"); });
dispatch_barrier_async(concurrentQueue, ^{
sleep(1);
NSLog(@"I am barrier");
});
NSLog(@"當(dāng)前線程");
dispatch_async(concurrentQueue, ^{ NSLog(@"任務(wù)4"); });
dispatch_async(concurrentQueue, ^{ NSLog(@"任務(wù)5"); });
輸出結(jié)果:

dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQ", DISPATCH_QUEUE_CONCURRENT);
//以下任務(wù)
dispatch_async(concurrentQueue, ^{ NSLog(@"任務(wù)1"); });
dispatch_async(concurrentQueue, ^{ NSLog(@"任務(wù)2"); });
dispatch_async(concurrentQueue, ^{ NSLog(@"任務(wù)3"); });
dispatch_barrier_sync(concurrentQueue, ^{
sleep(1);
NSLog(@"I am barrier");
});
NSLog(@"當(dāng)前線程");
dispatch_async(concurrentQueue, ^{ NSLog(@"任務(wù)4"); });
dispatch_async(concurrentQueue, ^{ NSLog(@"任務(wù)5"); });
輸出結(jié)果

2.使用 dispatch_group
dispatch_queue_t queue = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"開始任務(wù)A");
[NSThread sleepForTimeInterval:3];
NSLog(@"任務(wù)A done.");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"開始任務(wù)B");
[NSThread sleepForTimeInterval:2];
NSLog(@"任務(wù)B done.");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"開始任務(wù)C");
[NSThread sleepForTimeInterval:1];
NSLog(@"任務(wù)C done.");
dispatch_group_leave(group);
});
dispatch_group_notify(group, queue, ^{
NSLog(@"開始任務(wù)D");
});
輸出如下:
開始任務(wù)C
開始任務(wù)B
開始任務(wù)A
任務(wù)C done.
任務(wù)B done.
任務(wù)A done.
開始任務(wù)D
第二種情況,任務(wù)依賴
A, B, C任務(wù)完成之后(A, B, C順序要求依次執(zhí)行), 進(jìn)行任務(wù)D
dispatch_queue_t queue = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_barrier_async(queue, ^{
NSLog(@"開始任務(wù)A");
[NSThread sleepForTimeInterval:1];
NSLog(@"任務(wù)A done.");
});
dispatch_barrier_async(queue, ^{
NSLog(@"開始任務(wù)B");
[NSThread sleepForTimeInterval:0.5];
NSLog(@"任務(wù)B done.");
});
dispatch_barrier_async(queue, ^{
NSLog(@"開始任務(wù)C");
[NSThread sleepForTimeInterval:0.2];
NSLog(@"任務(wù)C done.");
});
dispatch_barrier_async(queue, ^{
NSLog(@"開始任務(wù)D");
});
輸出如下:
開始任務(wù)A
任務(wù)A done.
開始任務(wù)B
任務(wù)B done.
開始任務(wù)C
任務(wù)C done.
開始任務(wù)D
在每個(gè)網(wǎng)絡(luò)請求開始前使用
dispatch_group_enter來進(jìn)行標(biāo)識,網(wǎng)絡(luò)請求有回調(diào)后使用dispatch_group_leave來進(jìn)行標(biāo)識,這樣就能保證group_notify在所有網(wǎng)絡(luò)請求都有回調(diào)之后才調(diào)用
5. GCD快速迭代
我們知道for循環(huán)中的代碼是串行執(zhí)行的,如果此時(shí)我們有一系列的耗時(shí)操作需要執(zhí)行,此時(shí)我們可以使用Dispatch_apply函數(shù),他可以異步執(zhí)行,同時(shí)可以利用多核優(yōu)勢,完美替代for循環(huán)。
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
NSLog(@"%zd = %@",index,[NSThread currentThread]);
});
執(zhí)行結(jié)果如下:

可以看到上述循環(huán)是在多個(gè)線程中并發(fā)執(zhí)行的。
6. 考題:猜測打印結(jié)果
考題一:
- (void)test{
NSLog(@"任務(wù)B");
}
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"任務(wù)A");
[self performSelector:@selector(test) withObject:nil afterDelay:1.0];
NSLog(@"任務(wù)C");
});
}
知道結(jié)果了么?

我們來看看打印結(jié)果:

為什么只輸出了任務(wù)A和任務(wù)C而沒有任務(wù)B呢?其實(shí)這里涉及到了
RunLoop的知識,因?yàn)?code>performSelector:withObject:afterDelay:的本質(zhì)是向RunLoop中添加定時(shí)器,而子線程中默認(rèn)是沒有開啟RunLoop的,所以這里我們需要稍微改動下代碼,如下;
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"任務(wù)A");
[self performSelector:@selector(hahha) withObject:nil afterDelay:1.0];
NSLog(@"任務(wù)C");
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
}
關(guān)于RunLoop 有興趣的朋友可以看看我的這篇文章: RunLoop的使用
考題二:
- (void)test{
NSLog(@"任務(wù)B");
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"任務(wù)A");
}];
[thread start];
[self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}
執(zhí)行結(jié)果:
[73860:11959832] 任務(wù)A
[73860:11959410] *** Terminating app due to uncaught exception 'NSDestinationInvalidException', reason: '*** -[ViewController performSelector:onThread:withObject:waitUntilDone:modes:]: target thread exited while waiting for the perform'
因?yàn)槲覀冊趫?zhí)行完[thread start];的時(shí)候執(zhí)行任務(wù)A,此時(shí)線程就被銷毀了,如果我們要在thread線程中執(zhí)行test方法需要保住該線程的命,即線程?;?/code>,代碼需要修改如下:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"任務(wù)A");
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
}];
[thread start];
[self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}
方案四:NSOperation和NSOperationQueue
NSOperation 是蘋果公司對 GCD面向?qū)ο蟮姆庋b,所以使用起來非常方便。
NSOperation和NSOperationQueue分別對應(yīng) GCD 的 任務(wù) 和 隊(duì)列 。
使用步驟大致如下:
- 先將需要執(zhí)行的操作封裝到一個(gè)NSOperation對象中
- 然后將NSOperation對象添加到NSOperationQueue中
- 系統(tǒng)會?動將NSOperationQueue中的NSOperation取出來
- 將取出的NSOperation封裝的操作放到?條新線程中執(zhí)?
1. 任務(wù)
NSOperation只是一個(gè)抽象類,所以不能封裝任務(wù)。
但是我們可以使用它的兩個(gè)子類對象:NSInvocationOperation、NSBlockOperation。
- NSInvocationOperation : 需要傳入一個(gè)方法名。
//1.創(chuàng)建NSInvocationOperation對象
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
//2.開始執(zhí)行
[operation start];
打印結(jié)果:

其實(shí)等價(jià)于[self run];在主線程中執(zhí)行。
如果我們想讓任務(wù)在子線程中執(zhí)行,我們需要創(chuàng)建一個(gè)NSOperationQueue,如下:
// 創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 創(chuàng)建操作
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
// 添加操作到隊(duì)列中,會自動異步執(zhí)行
[queue addOperation:operation];
打印結(jié)果:

注意:操作對象默認(rèn)在主線程中執(zhí)行,只有將NSOperation放到一個(gè) NSOperationQueue中,才會異步執(zhí)行操作
- NSBlockOperation:用來并發(fā)的執(zhí)行一個(gè)或者多個(gè)Block對象。
注意:addExecutionBlock:該方法只要NSBlockOperation封裝的操作數(shù) > 1,就會異步執(zhí)行操作
//1.創(chuàng)建NSBlockOperation對象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
//2.開始任務(wù)
[operation start];
打印結(jié)果:
<NSThread: 0x604000074780>{number = 1, name = main}
addExecutionBlock方式添加多個(gè)任務(wù):
NSBlockOperation *operation = [[NSBlockOperation alloc] init];
[operation addExecutionBlock:^{
//---下載圖片----1---<NSThread: 0x600000260fc0>{number = 1, name = main}
NSLog(@"---下載圖片----1---%@", [NSThread currentThread]);
}];//這種方式只有第一個(gè)是主線程,其余都是子線程
[operation addExecutionBlock:^{
//---下載圖片----2---<NSThread: 0x600000263f40>{number = 3, name = (null)}
NSLog(@"---下載圖片----2---%@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^{
//---下載圖片----3---<NSThread: 0x60800026c440>{number = 4, name = (null)}
NSLog(@"---下載圖片----3---%@", [NSThread currentThread]);
}];
[operation start];
2. 隊(duì)列
通過上面的介紹我們知道調(diào)用NSOperation對象的start()方法可以啟動任務(wù),但是這樣做他們默認(rèn)是 同步執(zhí)行 的。即使是addExecutionBlock方法,也會在 當(dāng)前線程和其他線程 中執(zhí)行,也就是說還是會占用當(dāng)前線程。此時(shí)我們就需要用到NSOperationQueue了。
只要任務(wù)添加到隊(duì)列,便會自動調(diào)用任務(wù)的start()方法
//1.創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"---下載圖片----1---%@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^{
NSLog(@"---下載圖片----2---%@", [NSThread currentThread]);
}];
// 2.添加操作到隊(duì)列中(自動異步執(zhí)行)
[queue addOperation:operation];
打印結(jié)果:

任務(wù)依賴
需求:此時(shí)有 3 個(gè)任務(wù),這三個(gè)任務(wù)因?yàn)楸容^耗時(shí),所以需要異步并發(fā)執(zhí)行。
任務(wù)一: 從服務(wù)器上下載一張圖片
任務(wù)二:給這張圖片加個(gè)水印
任務(wù)三:把圖片返回給服務(wù)器。
這時(shí)候就需要控制任務(wù)的執(zhí)行順序了
//1.任務(wù)一:下載圖片
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下載圖片 - %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
//2.任務(wù)二:打水印
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"打水印 - %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
//3.任務(wù)三:上傳圖片
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"上傳圖片 - %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
//4.設(shè)置依賴
[operation2 addDependency:operation1]; //任務(wù)二依賴任務(wù)一
[operation3 addDependency:operation2]; //任務(wù)三依賴任務(wù)二
//5.創(chuàng)建隊(duì)列并加入任務(wù)
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];
打印結(jié)果

- 注意:不能添加相互依賴,比如 A依賴B,B又依賴A,否則會造成死鎖
1. 從其他線程回到主線程的方法
我們都知道在其他線程操作完成后必須到主線程更新UI。所以,介紹完所有的多線程方案后,我們來看看有哪些方法可以回到主線程。
- NSThread
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:NO];
- GCD
dispatch_async(dispatch_get_main_queue(), ^{
});
- NSOperationQueue
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
}];
2. 延遲執(zhí)行方案
公用延遲執(zhí)行方法
- (void)delayMethod{
NSLog(@"delayMethodEnd");
}
線程阻塞式
1.NSThread線程的sleep
[NSThread sleepForTimeInterval:2.0];
此方法是一種阻塞執(zhí)行方式,建議放在子線程中執(zhí)行,否則會卡住界面。但有時(shí)還是需要阻塞執(zhí)行,比如進(jìn)入歡迎界面需要沉睡2秒才進(jìn)入主界面時(shí)。
非阻塞執(zhí)行方式
performSelector
[self performSelector:@selector(delayMethod) withObject:nil afterDelay:2.0];
NSTimer定時(shí)器
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(delayMethod) userInfo:nil repeats:NO];
-
GCD的方式
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 NSEC_PER_SEC));
dispatch_after(delayTime, dispatch_get_main_queue(), ^{
[weakSelf delayMethod];
});
此方法可以在參數(shù)中選擇執(zhí)行的線程,是一種非阻塞執(zhí)行方式
GCD和NSOperation的比較
GCD 和 NSOperation的區(qū)別主要表現(xiàn)在以下幾方面:
1、GCD是一套 C 語言API,執(zhí)行和操作簡單高效,NSOperation底層也通過GCD實(shí)現(xiàn),這是他們之間最本質(zhì)的區(qū)別,因此如果希望自定義任務(wù),建議使用NSOperation;
2、依賴關(guān)系,NSOpeartion可以通過addDependency來添加任務(wù)的依賴,GCD需要添加依賴只能通過dispatch_barrier_async
3、KVO(鍵值對觀察),可以監(jiān)測operation是否正在執(zhí)行(isExecuted)、是否結(jié)束(isFinished),是否取消(isCanceld)對此GCD無法通過KVO進(jìn)行判斷;
4、優(yōu)先級,NSOpeartion可以設(shè)置queuePriority來設(shè)置優(yōu)先級,跳轉(zhuǎn)任務(wù)的執(zhí)行先后順序,GCD只能設(shè)置隊(duì)列的優(yōu)先級,且任務(wù)是根據(jù)先進(jìn)先出FIFO的原則來執(zhí)行的,不能設(shè)置任務(wù)的優(yōu)先級。
5、繼承,NSOperation是一個(gè)抽象類。實(shí)際開發(fā)中常用的是它的兩個(gè)子類:NSInvocationOperation和NSBlockOperation,同樣我們可以自定義NSOperation,GCD執(zhí)行任務(wù)可以自由組裝,沒有繼承那么高的代碼復(fù)用度;
6、效率,直接使用GCD效率確實(shí)會更高效,NSOperation會多一點(diǎn)開銷,但是通過NSOperation可以獲得依賴,優(yōu)先級,繼承,鍵值對觀察這些優(yōu)勢,相對于多的那么一點(diǎn)開銷確實(shí)很劃算,魚和熊掌不可得兼,取舍在于開發(fā)者自己;
7、NSOperation可以設(shè)置暫停,掛起等操作,可以隨時(shí)取消準(zhǔn)備執(zhí)行的任務(wù)(已經(jīng)在執(zhí)行的不能取消),GCD沒法停止已經(jīng)加入queue 的 block(雖然也能實(shí)現(xiàn),但是需要很復(fù)雜的代碼)
基于GCD簡單高效,更強(qiáng)的執(zhí)行能力,操作不太復(fù)雜的時(shí)候,優(yōu)先選用GCD;而比較復(fù)雜的任務(wù)可以自己通過NSOperation實(shí)現(xiàn)。
8、NSOperation可以設(shè)置最大任務(wù)數(shù),
多線程的安全隱患
當(dāng)多個(gè)線程同時(shí)訪問同一個(gè)資源時(shí),很容易引發(fā)數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全問題,比如下圖:

那么我們該如何去解決這個(gè)問題呢?
我們可以使用線程同步技術(shù)。所謂同步,就是協(xié)同步調(diào),按預(yù)定的先后次序進(jìn)行。常見的線程同步技術(shù)就是加鎖

關(guān)于鎖的實(shí)現(xiàn)方案 網(wǎng)上有很多,這里我就不再列舉了,可以參考:
iOS中保證線程安全的幾種方式與性能對比
iOS 常見知識點(diǎn)(三):Lock
深入理解iOS開發(fā)中的鎖