iOS開(kāi)發(fā)多線程總是繞不過(guò)的坎掘殴,看了很多前輩們優(yōu)秀的文章赚瘦,如:關(guān)于iOS多線程,我說(shuō)奏寨,你聽(tīng)起意,沒(méi)準(zhǔn)你就懂了!病瞳、談iOS多線程(NSThread揽咕、NSOperation悲酷、GCD)編程、iOS多線程:『GCD』詳盡總結(jié)亲善、iOS多線程:『pthread舔涎、NSThread』詳盡總結(jié)、iOS多線程:『NSOperation逗爹、NSOperationQueue』詳盡總結(jié)亡嫌、關(guān)于iOS多線程,你看我就夠了等掘而,但不自己整理一下挟冠,不敲一下代碼總是感覺(jué)不深刻 ,于是就有這篇文章袍睡,斷斷續(xù)續(xù)整理了好久知染。
示例我盡量把不同知識(shí)點(diǎn)代碼獨(dú)立開(kāi),看著簡(jiǎn)單一些斑胜,容易理解控淡。示例代碼我都用OC和Swift分別寫了,不過(guò)文中都是以O(shè)C說(shuō)明的止潘,因?yàn)槿绻脙煞N語(yǔ)言一起掺炭,看起來(lái)可能比較亂,文章也會(huì)更加長(zhǎng)(已經(jīng)非常長(zhǎng)了????)凭戴。
Swift寫法可能有比較大區(qū)別涧狮,也有些功能取消,但看一下代碼應(yīng)該能明白了么夫。
文中的所有示例代碼:andyRon/iOS-Multithreading
由于簡(jiǎn)書沒(méi)有Markdown的目錄功能者冤,我截了Typora的大綱,先看個(gè)大概:
1 簡(jiǎn)介
一些幫助理解的概念档痪,可以先簡(jiǎn)單看一下涉枫,然后讀完全文再回來(lái)看一下這一部分,可能就比較清晰了腐螟。??
1.1 一些概念
系統(tǒng)中正在運(yùn)行的每一個(gè)應(yīng)用程序都是一個(gè) 進(jìn)程(Process) 愿汰,每個(gè)進(jìn)程系統(tǒng)都會(huì)分配給它獨(dú)立的內(nèi)存運(yùn)行。也就是說(shuō)遭垛,在iOS系統(tǒng)中中尼桶,每一個(gè)應(yīng)用都是一個(gè)進(jìn)程。
一個(gè)進(jìn)程的所有任務(wù)都在 線程(Thread) 中進(jìn)行锯仪,因此每個(gè)進(jìn)程至少要有一個(gè)線程泵督,也就是主線程。那多線程其實(shí)就是一個(gè)進(jìn)程開(kāi)啟多條線程庶喜,讓所有任務(wù)并發(fā)執(zhí)行小腊。
iOS App一旦運(yùn)行救鲤,默認(rèn)就會(huì)開(kāi)啟一條線程。這條線程秩冈,通常稱作為“主線程”本缠。在iOS應(yīng)用中主線程的作用一般是:
刷新UI;
處理UI事件入问,例如點(diǎn)擊丹锹、滾動(dòng)、拖拽芬失。如果主線程的操作太多楣黍、太耗時(shí),就會(huì)造成App卡頓現(xiàn)象嚴(yán)重棱烂。所以租漂,通常我們都會(huì)把耗時(shí)的操作放在子線程中進(jìn)行,獲取到結(jié)果之后颊糜,回到主線程去刷新UI哩治。
多線程在一定意義上實(shí)現(xiàn)了進(jìn)程內(nèi)的資源共享,以及效率的提升衬鱼。同時(shí)业筏,在一定程度上相對(duì)獨(dú)立,它是程序執(zhí)行流的最小單元馁启,是進(jìn)程中的一個(gè)實(shí)體驾孔,是執(zhí)行程序最基本的單元,有自己棧和寄存器惯疙。
同步: 只能在當(dāng)前線程按先后順序依次執(zhí)行,不開(kāi)啟新線程妖啥。
異步: 可以在當(dāng)前線程開(kāi)啟多個(gè)新線程執(zhí)行霉颠,可不按順序執(zhí)行。
隊(duì)列: 裝載線程任務(wù)的隊(duì)形結(jié)構(gòu)荆虱。
并發(fā): 線程執(zhí)行可以同時(shí)一起進(jìn)行執(zhí)行蒿偎。
串行: 線程執(zhí)行只能依次逐一先后有序的執(zhí)行。
通過(guò)確保主線程自由響應(yīng)用戶事件怀读,并發(fā)可以很好地提高應(yīng)用的響應(yīng)性诉位。通過(guò)將工作分配到多核,還能提高應(yīng)用處理的性能菜枷。但是并發(fā)也帶來(lái)一定的額外開(kāi)銷(調(diào)度)苍糠,并且使代碼更加復(fù)雜,更難編寫和調(diào)試啤誊。
1.2 多線程概念補(bǔ)充
-
多線程的原理:
同一時(shí)間岳瞭,CPU只能處理一條線程拥娄,也就是只有一條線程在工作。所謂多線程并發(fā)(同時(shí))執(zhí)行瞳筏,其實(shí)是CPU快速的在多線程之間調(diào)度(切換)稚瘾。如果CPU調(diào)度線程的時(shí)間足夠快,就造成了多線程并發(fā)執(zhí)行的假象姚炕。
在實(shí)際項(xiàng)目開(kāi)發(fā)中并不是線程越多越好摊欠,如果開(kāi)了大量的線程,會(huì)消耗大量的CPU資源柱宦,CPU會(huì)被累死些椒,所以一般手機(jī)只開(kāi)1~3個(gè)線程為宜,不超過(guò)5個(gè)捷沸。
-
多線程的優(yōu)點(diǎn):
能適當(dāng)提高程序的執(zhí)行效率
能適當(dāng)提高資源的利用率摊沉,這個(gè)利用率表現(xiàn)在(CPU,內(nèi)存的利用率) -
多線程的缺點(diǎn):
開(kāi)啟線程需要占用一定的內(nèi)存空間(默認(rèn)情況下痒给,主線程占用1M说墨,子線程占用512KB,如果開(kāi)啟大量的線程苍柏,會(huì)占用大量的內(nèi)存空間尼斧,降低程序的性能)
線程越多,CPU在調(diào)度線程上的開(kāi)銷就越大
線程越多试吁,程序設(shè)計(jì)就越復(fù)雜棺棵,比如線程之間的通信,多線程的數(shù)據(jù)共享熄捍,這些都需要程序的處理烛恤,增加了程序的復(fù)雜度。
-
在iOS開(kāi)發(fā)中使用線程的注意事項(xiàng):
- 別將比較耗時(shí)的操作放在主線程中
- 耗時(shí)操作會(huì)卡住主線程余耽,嚴(yán)重影響UI的流暢度缚柏,給用戶一種“卡”的壞體驗(yàn)
2 四種解決方案對(duì)比
- 目前iOS多線程有四種方法:pthread,NSThread碟贾,GCD币喧, NSOperation,四種方案的簡(jiǎn)單對(duì)比一下袱耽。
由于pthread平常幾乎用不到杀餐,我暫時(shí)就不學(xué)了。
- 每個(gè)NSThread對(duì)象對(duì)應(yīng)一個(gè)線程朱巨,真正最原始的線程史翘,相對(duì)簡(jiǎn)單,但是需要手動(dòng)管理所有的線程活動(dòng),如生命周期恶座、線程同步搀暑、睡眠等。
- 怎么選擇 跨琳?
簡(jiǎn)單而安全的選擇NSOperation實(shí)現(xiàn)多線程即可自点。
處理大量并發(fā)數(shù)據(jù),又追求性能效率的選擇GCD脉让。
3 NSTread
生命周期還是需要程序員手動(dòng)管理桂敛,所以這套方案也是偶爾用用,不過(guò)獲取當(dāng)前線程([NSThread currentThread]
)和獲取主線程([NSThread mainThread]
)的這兩個(gè)方法會(huì)經(jīng)常使用溅潜。
3.1 NSThread三種線程開(kāi)啟方式
- 動(dòng)態(tài)開(kāi)啟
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething1:) object:(@"NSThread1")];
[thread1 start];
- 靜態(tài)開(kāi)啟
// 創(chuàng)建好之后直接啟動(dòng)
[NSThread detachNewThreadSelector:@selector(doSomething2:) toTarget:self withObject:(@"NSTread2")];
- 隱式開(kāi)啟
// 創(chuàng)建好之后也是直接啟動(dòng)
[self performSelectorInBackground:@selector(doSomething3:) withObject:(@"NSTread3")];
3.2 NSThread拓展
- 獲取當(dāng)前線程
NSThread *current = [NSThread currentThread];
- 獲取主線程
NSThread *main = [NSThread mainThread];
- 暫停當(dāng)前線程一段時(shí)間
[NSThread sleepForTimeInterval:2];
- 暫停當(dāng)前線程到某個(gè)時(shí)間
[NSThread sleepUntilDate: date];
- 線程之間通信
//在指定線程上執(zhí)行操作
[self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:YES];
//在主線程上執(zhí)行操作
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES];
//在當(dāng)前線程執(zhí)行操作
[self performSelector:@selector(run) withObject:nil];
4. GCD
GCD為Grand Central Dispatch的縮寫术唬。Grand Central Dispatch (GCD)是Apple開(kāi)發(fā)的一個(gè)多核編程的較新的解決方法。它主要用于優(yōu)化應(yīng)用程序以支持多核處理器以及其他對(duì)稱多處理系統(tǒng)滚澜。
4.1 GCD的優(yōu)點(diǎn)
- GCD 可用于多核的并行運(yùn)算
- GCD 會(huì)自動(dòng)利用更多的 CPU 內(nèi)核(比如雙核粗仓、四核)
- GCD 會(huì)自動(dòng)管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)设捐、銷毀線程)
- 程序員只需要告訴 GCD 想要執(zhí)行什么任務(wù)借浊,不需要編寫任何線程管理代碼
4.2 任務(wù)和隊(duì)列
-
任務(wù): 表現(xiàn)上就是一段代碼,OC就對(duì)應(yīng)一個(gè)Block萝招。任務(wù) 有兩種執(zhí)行方式蚂斤,是否會(huì)創(chuàng)建新的線程,會(huì)不會(huì)阻塞當(dāng)前線程
- 同步執(zhí)行(sync):在當(dāng)前線程執(zhí)行任務(wù)槐沼,不會(huì)開(kāi)辟新的線程曙蒸。必須等到Block函數(shù)執(zhí)行完畢后,dispatch函數(shù)才會(huì)返回岗钩。
- 異步執(zhí)行(async):可以在新的線程中執(zhí)行任務(wù)纽窟,但不一定會(huì)開(kāi)辟新的線程。dispatch函數(shù)會(huì)立即返回, 然后Block在后臺(tái)異步執(zhí)行兼吓。
-
隊(duì)列:任務(wù)管理方式师倔。分為 串行 和 并行兩種方式,都是按照 FIFO(先進(jìn)先出)原則依次觸發(fā)任務(wù)周蹭。
- 串行隊(duì)列 : 所有任務(wù)會(huì)在一條線程中執(zhí)行(有可能是當(dāng)前線程也有可能是新開(kāi)辟的線程),并且一個(gè)任務(wù)執(zhí)行完畢后疲恢,才開(kāi)始執(zhí)行下一個(gè)任務(wù)凶朗。(等待完成)
- 并行隊(duì)列: 可以開(kāi)啟多條線程并行執(zhí)行任務(wù)(但不一定會(huì)開(kāi)啟新的線程),并且當(dāng)一個(gè)任務(wù)放到指定線程開(kāi)始執(zhí)行時(shí)显拳,下一個(gè)任務(wù)就可以開(kāi)始執(zhí)行了棚愤。(等待發(fā)生)
兩者的區(qū)別:執(zhí)行順序不同,以及開(kāi)啟線程數(shù)不同。
-
兩個(gè)特殊隊(duì)列:
- 主隊(duì)列: 系統(tǒng)創(chuàng)建好的一個(gè) 串行隊(duì)列宛畦,它管理必須在主線程中執(zhí)行的任務(wù)瘸洛。
- 全局隊(duì)列:系統(tǒng)為我們創(chuàng)建好的一個(gè)并行隊(duì)列,使用起來(lái)與我們自己創(chuàng)建的并行隊(duì)列無(wú)本質(zhì)差別次和。
不同隊(duì)列創(chuàng)建獲取方式:
- (void)create {
// dispatch_queue_create 第一個(gè)參數(shù)是隊(duì)列名字反肋,一般用app的Bundle Identifier命名方式命名;第二個(gè)參數(shù)為NULL時(shí)表是串行隊(duì)列
//串行隊(duì)列
dispatch_queue_t serialQueue = dispatch_queue_create("q1.andyron.com", NULL);
dispatch_queue_t serialQueue2 = dispatch_queue_create("q2.andyron.com", DISPATCH_QUEUE_SERIAL);
//并行隊(duì)列
dispatch_queue_t concurrentQueue = dispatch_queue_create("q3.andyron.com", DISPATCH_QUEUE_CONCURRENT);
//全局并行隊(duì)列 DISPATCH_QUEUE_PRIORITY_DEFAULT表示優(yōu)先級(jí)
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//主隊(duì)列獲取
dispatch_queue_t mainQueue = dispatch_get_main_queue();
}
同步執(zhí)行 | 異步執(zhí)行 | |
---|---|---|
串行隊(duì)列 | 當(dāng)前線程踏施,一個(gè)一個(gè)執(zhí)行 | 其他線程石蔗,一個(gè)一個(gè)執(zhí)行 |
并行隊(duì)列 | 當(dāng)前線程,一個(gè)一個(gè)執(zhí)行 | 開(kāi)很多線程畅形,一起執(zhí)行 |
4.3 下面??以一個(gè)一個(gè)??來(lái)學(xué)習(xí)GCD养距,幫助搞清上面的概念
- 例子一:線程死鎖(主隊(duì)列 + 同步執(zhí)行)
- (void)case1 {
NSLog(@"A=====%@", [NSThread currentThread]);
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"B=====%@", [NSThread currentThread]);
});
NSLog(@"C=====%@", [NSThread currentThread]);
}
運(yùn)行結(jié)果崩潰:
打印結(jié)果:
GCD(OC)[51511:6351422] A=====<NSThread: 0x600000064340>{number = 1, name = main}
解釋:默認(rèn)就一個(gè)主隊(duì)列和一個(gè)主線程,因此case1
函數(shù)這段任務(wù)就在主隊(duì)列中同步執(zhí)行日熬,dispatch_sync...
這段代碼表示把B處任務(wù)加入主隊(duì)列中棍厌,并且同步執(zhí)行,這就出問(wèn)題竖席,B處任務(wù)要等主隊(duì)列中同步執(zhí)行之前的case1
這段任務(wù)結(jié)束后執(zhí)行耘纱,但B處任務(wù)在case1
這段任務(wù)中,case1
又要等B處任務(wù)執(zhí)行完才能繼續(xù)執(zhí)行怕敬。case1
任務(wù)要等B處完成才能繼續(xù)揣炕,但case1
又排在B處前面,這就尷尬了东跪, ̄□ ̄||畸陡,因此崩潰了??
- 例子二:主隊(duì)列 + 異步執(zhí)行
- (void)case2 {
NSLog(@"A=====%@", [NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"B=====%@", [NSThread currentThread]);
});
NSLog(@"C=====%@", [NSThread currentThread]);
}
結(jié)果:
GCD(OC)[52567:6465354] A=====<NSThread: 0x600000068d40>{number = 1, name = main}
GCD(OC)[52567:6465354] C=====<NSThread: 0x600000068d40>{number = 1, name = main}
GCD(OC)[52567:6465354] B=====<NSThread: 0x600000068d40>{number = 1, name = main}
解釋:任務(wù)都在主隊(duì)列(串行),而且只要一個(gè)主線程(name都是main)虽填,B處任務(wù)由于是異步執(zhí)行丁恭,等case2任務(wù)完成后執(zhí)行。
- 例子三:串行隊(duì)列 + 同步執(zhí)行
// 串行隊(duì)列 + 同步執(zhí)行
- (void)case3 {
dispatch_queue_t serialQueue = dispatch_queue_create("q2.andyron.com", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{
NSLog(@"1======%@", [NSThread currentThread]);
});
dispatch_sync(serialQueue, ^{
NSLog(@"2======%@", [NSThread currentThread]);
});
dispatch_sync(serialQueue, ^{
NSLog(@"3======%@", [NSThread currentThread]);
});
NSLog(@"4======%@", [NSThread currentThread]);
}
結(jié)果:
GCD(OC)[53734:6582112] 1======<NSThread: 0x604000261700>{number = 1, name = main}
GCD(OC)[53734:6582112] 2======<NSThread: 0x604000261700>{number = 1, name = main}
GCD(OC)[53734:6582112] 3======<NSThread: 0x604000261700>{number = 1, name = main}
GCD(OC)[53734:6582112] 4======<NSThread: 0x604000261700>{number = 1, name = main}
解釋:??1中的主隊(duì)列也是串行隊(duì)列斋日,但和這邊不同牲览,這邊是新建了另一個(gè)串行隊(duì)列,不會(huì)出現(xiàn)沖突恶守,并且都在主線程中運(yùn)行第献,這也說(shuō)明了同步執(zhí)行不具備創(chuàng)建新線程的能力。
- 列子四:串行隊(duì)列 + 異步執(zhí)行
// 串行隊(duì)列 + 異步執(zhí)行
- (void)case4 {
dispatch_queue_t serialQueue = dispatch_queue_create("q2.andyron.com", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
NSLog(@"1========%@",[NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
NSLog(@"2========%@",[NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
NSLog(@"3========%@",[NSThread currentThread]);
});
NSLog(@"4========%@",[NSThread currentThread]);
}
打印結(jié)果:
GCD(OC)[53970:6604711] 4========<NSThread: 0x60000007c880>{number = 1, name = main}
GCD(OC)[53970:6604933] 1========<NSThread: 0x600000460280>{number = 3, name = (null)}
GCD(OC)[53970:6604933] 2========<NSThread: 0x600000460280>{number = 3, name = (null)}
GCD(OC)[53970:6604933] 3========<NSThread: 0x600000460280>{number = 3, name = (null)}
解釋:現(xiàn)在打印了4兔港,后打印了1庸毫,2,3衫樊,這是一部執(zhí)行的結(jié)果飒赃,并且4在主線程利花,其它在子線程打印,這也說(shuō)明了異步執(zhí)行可以創(chuàng)建新線程载佳。
- 列子五:并行隊(duì)列 + 同步執(zhí)行
/// 并行隊(duì)列 + 同步執(zhí)行
- (void)case5 {
dispatch_queue_t concurrentQueue = dispatch_queue_create("q3.andyron.com", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(concurrentQueue, ^{
NSLog(@"1========%@",[NSThread currentThread]);
//[self nslogCount:10000 number:1];
});
dispatch_sync(concurrentQueue, ^{
NSLog(@"2========%@",[NSThread currentThread]);
//[self nslogCount:10000 number:2];
});
dispatch_sync(concurrentQueue, ^{
NSLog(@"3========%@",[NSThread currentThread]);
//[self nslogCount:10000 number:3];
});
NSLog(@"4========%@",[NSThread currentThread]);
}
打印結(jié)果:
GCD(OC)[54401:6646454] 1========<NSThread: 0x600000260e80>{number = 1, name = main}
GCD(OC)[54401:6646454] 2========<NSThread: 0x600000260e80>{number = 1, name = main}
GCD(OC)[54401:6646454] 3========<NSThread: 0x600000260e80>{number = 1, name = main}
GCD(OC)[54401:6646454] 4========<NSThread: 0x600000260e80>{number = 1, name = main}
解釋:都在主線程執(zhí)行炒事,由于只有一個(gè)線程,結(jié)果看上去是順序執(zhí)行蔫慧。
- 列子六: 并行隊(duì)列 + 異步執(zhí)行
// 并行隊(duì)列 + 異步執(zhí)行
- (void)case6 {
dispatch_queue_t concurrentQueue = dispatch_queue_create("q3.andyron.com", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
NSLog(@"1========%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"2========%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"3========%@",[NSThread currentThread]);
});
NSLog(@"4========%@",[NSThread currentThread]);
}
打印結(jié)果:
GCD(OC)[54687:6675227] 2========<NSThread: 0x600000466600>{number = 5, name = (null)}
GCD(OC)[54687:6675036] 4========<NSThread: 0x604000078d00>{number = 1, name = main}
GCD(OC)[54687:6675226] 1========<NSThread: 0x600000463fc0>{number = 4, name = (null)}
GCD(OC)[54687:6675229] 3========<NSThread: 0x60400067d400>{number = 6, name = (null)}
解釋:除了打印4的是主線挠乳,其他又開(kāi)啟了三個(gè)線程來(lái)執(zhí)行三個(gè)任務(wù),當(dāng)天開(kāi)啟幾個(gè)線程是有CPU自己決定的藕漱,任務(wù)的執(zhí)行是隨機(jī)的欲侮。
4.4 GCD 重點(diǎn)
- 只要是串行隊(duì)列,肯定要等上一個(gè)任務(wù)執(zhí)行完成肋联,才能開(kāi)始下一個(gè)任務(wù)威蕉。但是并行隊(duì)列當(dāng)上一個(gè)任務(wù)開(kāi)始執(zhí)行后,下一個(gè)任務(wù)就可以開(kāi)始執(zhí)行橄仍。
- 同步+串行:未開(kāi)辟新線程材泄,串行執(zhí)行任務(wù)竞漾;同步+并行:未開(kāi)辟新線程娄蔼,串行執(zhí)行任務(wù)赖条;異步+串行:新開(kāi)辟一條線程,串行執(zhí)行任務(wù)宪哩;異步+并行:開(kāi)辟多條新線程娩贷,并行執(zhí)行任務(wù);在主線程中同步使用主隊(duì)列執(zhí)行任務(wù)锁孟,會(huì)造成死鎖彬祖。
4.5 GCD其他相關(guān)方法
- 延遲執(zhí)行方法:
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
指定時(shí)間后執(zhí)行某個(gè)任務(wù),dispatch_after
函數(shù)指定的時(shí)間是指多長(zhǎng)后將任務(wù)加到某個(gè)隊(duì)列中品抽,而不是具體執(zhí)行時(shí)間储笑,具體時(shí)間要看CPU執(zhí)行時(shí)間了,可以看做是個(gè)大約延遲時(shí)間圆恤。
- (void)after {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2.0秒后異步追加任務(wù)代碼到主隊(duì)列突倍,并開(kāi)始執(zhí)行
NSLog(@"after---%@",[NSThread currentThread]); // 打印當(dāng)前線程
});
}
-
dispatch_once
:在生命周期內(nèi)只執(zhí)行一次。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"就一次%@",[NSThread currentThread]);
});
}
不管點(diǎn)擊多少次只有一次輸出盆昙。
GCD的內(nèi)容很豐富羽历,還有很多函數(shù),2016和2017的WWDC都有專門講到GCD淡喜,想更近一步可以參考:Modernizing Grand Central Dispatch Usage窄陡,Concurrent Programming With GCD in Swift 3。
5 NSOperation 和 NSOperationQueue
5.1 概念
NSOperation
(操作) 和NSOperationQueue
(操作隊(duì)列) 是蘋果對(duì)GCD的封裝NSOperation
和NSOperationQueue
分別相當(dāng)于 GCD 的 任務(wù) 和 隊(duì)列NSOperation
只是一個(gè)抽象類拆火,不能直接使用,使用其 2 個(gè)子類:NSInvocationOperation
和NSBlockOperation
。NSOperation
的使用除了其現(xiàn)有的子類们镜,還可以自定義子類币叹。操作隊(duì)列通過(guò)設(shè)置最大并發(fā)操作數(shù)(maxConcurrentOperationCount)來(lái)控制并發(fā)、串行模狭。
NSOperationQueue 為我們提供了兩種不同類型的隊(duì)列:主隊(duì)列和自定義隊(duì)列颈抚。主隊(duì)列運(yùn)行在主線程之上,而自定義隊(duì)列在后臺(tái)執(zhí)行嚼鹉。
- NSOperation 需要配合 NSOperationQueue 來(lái)使用贩汉。否則,NSOperation 單獨(dú)使用時(shí)系統(tǒng)默認(rèn)同步執(zhí)行操作锚赤,配合 NSOperationQueue 我們能更好的實(shí)現(xiàn)異步執(zhí)行匹舞。
5. 2 NSOperation 實(shí)現(xiàn)多線程的步驟
- 創(chuàng)建操作:先將需要執(zhí)行的操作封裝到一個(gè) NSOperation 對(duì)象中。
- 創(chuàng)建隊(duì)列:創(chuàng)建 NSOperationQueue 對(duì)象线脚。
- 將操作加入到隊(duì)列中:將 NSOperation 對(duì)象添加到NSOperationQueue 對(duì)象中赐稽。
- 之后,系統(tǒng)就會(huì)自動(dòng)將 NSOperationQueue 中的 NSOperation 取出來(lái)浑侥,在新線程中執(zhí)行操作姊舵。
5.3 使用NSOperation的子類NSInvocationOperation
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
[operation start];
在沒(méi)有使用NSOperationQueue
時(shí),NSInvocationOperation
會(huì)在當(dāng)前線程(主線程或其他線程)內(nèi)運(yùn)行寓落。
5.4 使用NSOperation的子類NSBlockOperation
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
[operation start];
結(jié)果:
NSOperation(OC)[97406:5880178] 1---<NSThread: 0x60400006e700>{number = 1, name = main}
NSOperation(OC)[97406:5880178] 1---<NSThread: 0x60400006e700>{number = 1, name = main}
在沒(méi)有使用NSOperationQueue
時(shí)括丁,NSBlockOperation
也會(huì)在當(dāng)前線程(主線程或其他線程)內(nèi)運(yùn)行。
另外伶选,NSBlockOperation
還提供了一個(gè)方法 addExecutionBlock:
史飞,用來(lái)添加額外的操作:
// 1.創(chuàng)建 NSBlockOperation 對(duì)象
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}];
// 2.添加額外的操作
[op addExecutionBlock:^{
[NSThread sleepForTimeInterval:1]; // 模擬耗時(shí)操作
NSLog(@"2---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}];
[op addExecutionBlock:^{
[NSThread sleepForTimeInterval:1]; // 模擬耗時(shí)操作
NSLog(@"3---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}];
[op addExecutionBlock:^{
[NSThread sleepForTimeInterval:1]; // 模擬耗時(shí)操作
NSLog(@"4---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}];
[op addExecutionBlock:^{
[NSThread sleepForTimeInterval:1]; // 模擬耗時(shí)操作
NSLog(@"5---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}];
// 3.調(diào)用 start 方法開(kāi)始執(zhí)行操作
[op start];
某一次的運(yùn)行結(jié)果:
NSOperation(OC)[97709:5888750] 1---<NSThread: 0x600000469740>{number = 3, name = (null)}
NSOperation(OC)[97709:5888750] 5---<NSThread: 0x600000469740>{number = 3, name = (null)}
NSOperation(OC)[97709:5888748] 3---<NSThread: 0x600000469e40>{number = 4, name = (null)}
NSOperation(OC)[97709:5888498] 4---<NSThread: 0x60400007a600>{number = 1, name = main}
NSOperation(OC)[97709:5888758] 2---<NSThread: 0x604000463480>{number = 5, name = (null)}
addExecutionBlock:
添加的操作和之前blockOperationWithBlock
添加的操作是否在主線程或者是否開(kāi)多線程,是由系統(tǒng)決定考蕾,它們的地位是相同的祸憋,所以每一次執(zhí)行的結(jié)果可能不同。
5.5 NSOperation的自定義子類
除了上面兩個(gè)子類外肖卧,還可以通過(guò)重寫main
方法來(lái)自定義子類蚯窥。
#import "AROperation.h"
@implementation AROperation
- (void)main {
if (!self.isCancelled) {
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"自定義Operation---%@", [NSThread currentThread]);
}
}
}
@end
使用:
AROperation *op = [[AROperation alloc] init];
[op start];
5.6 NSOperationQueue
上面幾種情況都是沒(méi)有操作隊(duì)列,一般只在主線程運(yùn)行塞帐。而使用操作隊(duì)列就可以實(shí)現(xiàn)多線程了拦赠。操作隊(duì)列分兩種:
主隊(duì)列 : 凡是添加到主隊(duì)列中的操作,都會(huì)放到主線程中執(zhí)行
自定義隊(duì)列: 操作自動(dòng)放到子線程中執(zhí)行葵姥,同時(shí)包含了:串行荷鼠、并發(fā)功能。
5.6.1 創(chuàng)建隊(duì)列
// 主隊(duì)列獲取方法
NSOperationQueue *queue = [NSOperationQueue mainQueue];
// 自定義隊(duì)列創(chuàng)建方法
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
5.6.2 添加操作到隊(duì)列中
兩種不同的添加方法:
- (void)addOperation:(NSOperation *)op;
- (void)addOperationToQueue {
// 1.創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.創(chuàng)建操作
// 使用 NSInvocationOperation 創(chuàng)建操作1
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
// 使用 NSInvocationOperation 創(chuàng)建操作2
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task2) object:nil];
// 使用 NSBlockOperation 創(chuàng)建操作3
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"3---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
[op3 addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"4---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
// 3.使用 addOperation: 添加所有操作到隊(duì)列中
[queue addOperation:op1]; // [op1 start]
[queue addOperation:op2]; // [op2 start]
[queue addOperation:op3]; // [op3 start]
}
- (void) task1 {
NSLog(@"1---%@", [NSThread currentThread]);
}
- (void) task2 {
NSLog(@"2---%@", [NSThread currentThread]);
}
某一次的運(yùn)行結(jié)果:
NSOperation(OC)[7557:6218702] 2---<NSThread: 0x60400027f900>{number = 4, name = (null)}
NSOperation(OC)[7557:6218699] 1---<NSThread: 0x600000461a00>{number = 3, name = (null)}
NSOperation(OC)[7557:6218701] 4---<NSThread: 0x6040004606c0>{number = 5, name = (null)}
NSOperation(OC)[7557:6218700] 3---<NSThread: 0x604000460d40>{number = 6, name = (null)}
NSOperation(OC)[7557:6218700] 3---<NSThread: 0x604000460d40>{number = 6, name = (null)}
NSOperation(OC)[7557:6218701] 4---<NSThread: 0x6040004606c0>{number = 5, name = (null)}
并發(fā)運(yùn)行榔幸,執(zhí)行次序不確定允乐。
-
- (void)addOperationWithBlock:(void (^)(void))block;
不需要先創(chuàng)建操作矮嫉,直接添加block
- (void)addOperationWithBlockToQueue {
// 1.創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.使用 addOperationWithBlock: 添加操作到隊(duì)列中
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"2---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"3---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
}
某一次的運(yùn)行結(jié)果:
NSOperation(OC)[7772:6229770] 2---<NSThread: 0x600000277640>{number = 5, name = (null)}
NSOperation(OC)[7772:6229772] 1---<NSThread: 0x60400026bfc0>{number = 4, name = (null)}
NSOperation(OC)[7772:6229780] 3---<NSThread: 0x6040002686c0>{number = 3, name = (null)}
NSOperation(OC)[7772:6229770] 2---<NSThread: 0x600000277640>{number = 5, name = (null)}
NSOperation(OC)[7772:6229780] 3---<NSThread: 0x6040002686c0>{number = 3, name = (null)}
NSOperation(OC)[7772:6229772] 1---<NSThread: 0x60400026bfc0>{number = 4, name = (null)}
5.6.3 maxConcurrentOperationCount
NSOperationQueue
提供一個(gè)maxConcurrentOperationCount
(最大并發(fā)操作數(shù))屬性來(lái)控制串行還是并發(fā)。
maxConcurrentOperationCount
控制的不是并發(fā)線程的數(shù)量牍疏,而是一個(gè)隊(duì)列中同時(shí)能并發(fā)執(zhí)行的最大操作數(shù)蠢笋。而且一個(gè)操作也并非只能在一個(gè)線程中運(yùn)行。
maxConcurrentOperationCount
默認(rèn)情況下為-1鳞陨,表示不進(jìn)行限制昨寞。
- (void)setMaxConcurrentOperationCount {
// 1.創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.設(shè)置最大并發(fā)操作數(shù)
queue.maxConcurrentOperationCount = 1; // 串行隊(duì)列
// queue.maxConcurrentOperationCount = 2; // 并發(fā)隊(duì)列
// queue.maxConcurrentOperationCount = 8; // 并發(fā)隊(duì)列
// 3.添加操作
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"2---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"3---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"4---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
}
當(dāng)最大并發(fā)操作數(shù)為1時(shí),也就是串行執(zhí)行時(shí)厦滤,控制臺(tái)中返回的結(jié)果順序是固定的援岩。而大于1時(shí),也就是并發(fā)執(zhí)行掏导,每一次執(zhí)行的順序就可能不同(控制臺(tái)中返回的額結(jié)果順序可能不同)享怀。當(dāng)然開(kāi)啟的線程數(shù)量是有系統(tǒng)決定的。
5.7 NSOperation之間的依賴
并行時(shí)碘菜,各個(gè)的操作的執(zhí)行順序是有系統(tǒng)決定凹蜈,程序員不能直接控制。但是NSOperation提高了依賴忍啸,來(lái)解決這個(gè)問(wèn)題仰坦。相關(guān)方法和屬性:
-
- (void)addDependency:(NSOperation *)op;
添加依賴,使當(dāng)前操作依賴于操作 op 的完成计雌。 -
- (void)removeDependency:(NSOperation *)op;
移除依賴悄晃,取消當(dāng)前操作對(duì)操作 op 的依賴。 -
@property (readonly, copy) NSArray<NSOperation *> *dependencies;
在當(dāng)前操作開(kāi)始執(zhí)行之前完成執(zhí)行的所有操作對(duì)象數(shù)組凿滤。
- (void)addDependency {
// 1.創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.創(chuàng)建操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"2---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
// 3.添加依賴
[op2 addDependency:op1]; // 讓op2 依賴于 op1妈橄,則先執(zhí)行op1,再執(zhí)行op2
// 4.添加操作到隊(duì)列中
[queue addOperation:op1];
[queue addOperation:op2];
}
運(yùn)行結(jié)果是固定的:
NSOperation(OC)[8977:6302811] 1---<NSThread: 0x604000277f80>{number = 3, name = (null)}
NSOperation(OC)[8977:6302811] 1---<NSThread: 0x604000277f80>{number = 3, name = (null)}
NSOperation(OC)[8977:6302810] 2---<NSThread: 0x604000274240>{number = 4, name = (null)}
NSOperation(OC)[8977:6302810] 2---<NSThread: 0x604000274240>{number = 4, name = (null)}
注意
:不能添加相互依賴翁脆,會(huì)死鎖眷蚓,比如 A依賴B,B依賴A反番。
可以使用 removeDependency 來(lái)解除依賴關(guān)系沙热。
可以在不同的隊(duì)列之間依賴,反正就是這個(gè)依賴是添加到任務(wù)身上的罢缸,和隊(duì)列沒(méi)關(guān)系篙贸。
5.8 NSOperation 常用屬性和方法
取消操作方法
- (void)cancel;
實(shí)質(zhì)是標(biāo)記 isCancelled 狀態(tài)。-
判斷操作狀態(tài)方法
-
- (BOOL)isFinished;
判斷操作是否已經(jīng)結(jié)束枫疆。 -
- (BOOL)isCancelled;
判斷操作是否已經(jīng)標(biāo)記為取消爵川。 -
- (BOOL)isExecuting;
判斷操作是否正在在運(yùn)行。 -
- (BOOL)isReady;
判斷操作是否處于準(zhǔn)備就緒狀態(tài)息楔,這個(gè)值和操作的依賴關(guān)系相關(guān)寝贡。
-
-
操作同步
-
- (void)waitUntilFinished;
阻塞當(dāng)前線程扒披,直到該操作結(jié)束⊥酶剩可用于線程執(zhí)行順序的同步谎碍。 -
- (void)setCompletionBlock:(void (^)(void))block;
completionBlock 會(huì)在當(dāng)前操作執(zhí)行完畢時(shí)執(zhí)行 completionBlock。 -
- (void)addDependency:(NSOperation *)op;
添加依賴洞焙,使當(dāng)前操作依賴于操作 op 的完成。 -
- (void)removeDependency:(NSOperation *)op;
移除依賴拯啦,取消當(dāng)前操作對(duì)操作 op 的依賴澡匪。 -
@property (readonly, copy) NSArray<NSOperation *> *dependencies;
在當(dāng)前操作開(kāi)始執(zhí)行之前完成執(zhí)行的所有操作對(duì)象數(shù)組。
-
5.9 NSOperationQueue 常用屬性和方法
- 取消/暫停/恢復(fù)操作
-
- (void)cancelAllOperations;
可以取消隊(duì)列的所有操作褒链。 -
- (BOOL)isSuspended;
判斷隊(duì)列是否處于暫停狀態(tài)唁情。 YES 為暫停狀態(tài),NO 為恢復(fù)狀態(tài)甫匹。 -
- (void)setSuspended:(BOOL)b;
可設(shè)置操作的暫停和恢復(fù)甸鸟,YES 代表暫停隊(duì)列,NO 代表恢復(fù)隊(duì)列兵迅。
- 操作同步
-
- (void)waitUntilAllOperationsAreFinished;
阻塞當(dāng)前線程抢韭,直到隊(duì)列中的操作全部執(zhí)行完畢。
- 添加/獲取操作
-
- (void)addOperationWithBlock:(void (^)(void))block;
向隊(duì)列中添加一個(gè) NSBlockOperation 類型操作對(duì)象恍箭。 -
- (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait;
向隊(duì)列中添加操作數(shù)組刻恭,wait 標(biāo)志是否阻塞當(dāng)前線程直到所有操作結(jié)束 -
- (NSArray *)operations;
當(dāng)前在隊(duì)列中的操作數(shù)組(某個(gè)操作執(zhí)行結(jié)束后會(huì)自動(dòng)從這個(gè)數(shù)組清除)。 -
- (NSUInteger)operationCount;
當(dāng)前隊(duì)列中的操作數(shù)扯夭。
- 獲取隊(duì)列
-
+ (id)currentQueue;
獲取當(dāng)前隊(duì)列鳍贾,如果當(dāng)前線程不是在 NSOperationQueue 上運(yùn)行則返回 nil。 -
+ (id)mainQueue;
獲取主隊(duì)列交洗。
6 后記
雖然總結(jié)了很多骑科,但還有很多內(nèi)容沒(méi)有涉及和深入。
由于個(gè)人能力有限构拳,時(shí)間緊湊(實(shí)際我已經(jīng)花了很多時(shí)間?????♀????♀?)咆爽,文中難免有錯(cuò)誤,希望小伙伴批評(píng)指正隐圾。
參考:
關(guān)于iOS多線程伍掀,我說(shuō),你聽(tīng)暇藏,沒(méi)準(zhǔn)你就懂了蜜笤!
談iOS多線程(NSThread、NSOperation盐碱、GCD)編程
iOS多線程:『GCD』詳盡總結(jié)
iOS多線程:『pthread把兔、NSThread』詳盡總結(jié)
iOS多線程:『NSOperation沪伙、NSOperationQueue』詳盡總結(jié)
關(guān)于iOS多線程,你看我就夠了
Grand Central Dispatch (GCD) and Dispatch Queues in Swift 3
WWDC-2017-Modernizing Grand Central Dispatch Usage
本文標(biāo)題: 關(guān)于iOS多線程县好,這邊勉強(qiáng)可以看看(OC&Swift)
本文作者: AndyRon
本文鏈接: http://andyron.com/2018/ios-multithreading.html
版權(quán)聲明: 本博客所有文章除特別聲明外围橡,均采用 CC BY-NC-SA 3.0 許可協(xié)議。轉(zhuǎn)載請(qǐng)注明出處缕贡!