開(kāi)始之前
首先要解決一個(gè)大家對(duì)多線程的理解上可能存在的誤區(qū):新開(kāi)一個(gè)線程吠卷,能提高速度锡垄,避免阻塞主線程。
這句話看著好像是對(duì)著呢祭隔,但是仔細(xì)想想這句話是不那么準(zhǔn)確的。
舉個(gè)例子:一個(gè)主任務(wù)需要十個(gè)子任務(wù)按順序執(zhí)行來(lái)完成。現(xiàn)在有兩種方式完成這個(gè)任務(wù):
1.建十個(gè)線程疾渴,把每個(gè)子任務(wù)放在對(duì)應(yīng)的線程中執(zhí)行千贯。執(zhí)行完一個(gè)線程中的任務(wù)就切換到另一個(gè)線程。
2.把十個(gè)任務(wù)放在一個(gè)線程里搞坝,按順序執(zhí)行搔谴。
操作系統(tǒng)的基礎(chǔ)知識(shí)告訴我們,線程桩撮,是執(zhí)行程序最基本的單元敦第,它有自己棧和寄存器。說(shuō)得再具體一些店量,線程就是“一個(gè)CPU執(zhí)行的一條無(wú)分叉的命令列”芜果。
對(duì)于第一種方法,在十個(gè)線程之間來(lái)回切換融师,就意味著有十組棧和寄存器中的值需要不斷地被備份右钾、替換。 而對(duì)于對(duì)于第二種方法旱爆,只有一組寄存器和棧存在舀射,顯然效率完勝前者。
并發(fā)與并行的區(qū)別
并發(fā)指的是一種現(xiàn)象怀伦,一種經(jīng)常出現(xiàn)脆烟,無(wú)可避免的現(xiàn)象。它描述的是“多個(gè)任務(wù)同時(shí)發(fā)生房待,需要被處理”這一現(xiàn)象邢羔。它的側(cè)重點(diǎn)在于“發(fā)生”。
比如有很多人排隊(duì)等待檢票吴攒,這一現(xiàn)象就可以理解為并發(fā)张抄。
并行指的是一種技術(shù),一個(gè)同時(shí)處理多個(gè)任務(wù)的技術(shù)洼怔。它描述了一種能夠同時(shí)處理多個(gè)任務(wù)的能力署惯,側(cè)重點(diǎn)在于“運(yùn)行”。
比如景點(diǎn)開(kāi)放了多個(gè)檢票窗口镣隶,同一時(shí)間內(nèi)能服務(wù)多個(gè)游客极谊。這種情況可以理解為并行。
并行的反義詞就是串行安岂,表示任務(wù)必須按順序來(lái)轻猖,一個(gè)一個(gè)執(zhí)行,前一個(gè)執(zhí)行完了才能執(zhí)行后一個(gè)域那。
我們經(jīng)常提到的“多線程”咙边,正是采用了并行技術(shù),從而提高了執(zhí)行效率。因?yàn)橛卸鄠€(gè)線程败许,所以計(jì)算機(jī)的多個(gè)CPU可以同時(shí)工作王带,同時(shí)處理不同線程內(nèi)的指令。
并發(fā)是一種現(xiàn)象市殷,面對(duì)這一現(xiàn)象愕撰,我們首先創(chuàng)建多個(gè)線程,真正加快程序運(yùn)行速度的醋寝,是并行技術(shù)搞挣。也就是讓多個(gè)CPU同時(shí)工作。而多線程音羞,是為了讓多個(gè)CPU同時(shí)工作成為可能囱桨。
同步與異步
同步方法就是我們平時(shí)調(diào)用的哪些方法。比如在第一行調(diào)用a方法黄选,那么程序運(yùn)行到第二行的時(shí)候蝇摸,a方法肯定是執(zhí)行完了。
所謂的異步办陷,就是允許在執(zhí)行某一個(gè)任務(wù)時(shí)貌夕,函數(shù)立刻返回,但是真正要執(zhí)行的任務(wù)稍后完成民镜。
比如我們?cè)邳c(diǎn)擊保存按鈕之后啡专,要先把數(shù)據(jù)寫到內(nèi)存,然后更新UI制圈。同步方法就是等到數(shù)據(jù)保存完再更新UI们童,而異步則是立刻從保存數(shù)據(jù)的方法返回并向后執(zhí)行代碼,同時(shí)真正用來(lái)保存數(shù)據(jù)的指令將在稍后執(zhí)行鲸鹦。
區(qū)別和聯(lián)系
串行/并行針對(duì)的是隊(duì)列慧库,而同步/異步,針對(duì)的則是線程馋嗜。最大的區(qū)別在于齐板,同步線程要阻塞當(dāng)前線程,必須要等待同步線程中的任務(wù)執(zhí)行完葛菇,返回以后甘磨,才能繼續(xù)執(zhí)行下一任務(wù);而異步線程則是不用等待眯停。
假設(shè)現(xiàn)在有三個(gè)任務(wù)需要處理济舆。假設(shè)單個(gè)CPU處理它們分別需要3、1莺债、1秒滋觉。
并行/串行討論的是處理這三個(gè)任務(wù)的速度問(wèn)題签夭。如果三個(gè)CPU并行處理,那么一共只需要3秒椎瘟。相比于串行處理覆致,節(jié)約了兩秒侄旬。
同步/異步描述的是任務(wù)之間先后順序問(wèn)題肺蔚。假設(shè)需要三秒的那個(gè)是保存數(shù)據(jù)的任務(wù),而另外兩個(gè)是UI相關(guān)的任務(wù)儡羔。那么通過(guò)異步執(zhí)行第一個(gè)任務(wù)宣羊,我們省去了三秒鐘的卡頓時(shí)間。
對(duì)于同步執(zhí)行的三個(gè)任務(wù)來(lái)說(shuō)汰蜘,系統(tǒng)傾向于在同一個(gè)線程里執(zhí)行它們仇冯。因?yàn)榧词归_(kāi)了三個(gè)線程,也得等他們分別在各自的線程中完成族操。并不能減少總的處理時(shí)間苛坚,反而徒增了線程切換所耗費(fèi)的時(shí)間。
對(duì)于異步執(zhí)行的三個(gè)任務(wù)來(lái)說(shuō)色难,系統(tǒng)傾向于在三個(gè)新的線程里執(zhí)行他們泼舱。因?yàn)檫@樣可以最大程度的利用CPU性能,提升程序運(yùn)行效率枷莉。
總結(jié)
在需要同時(shí)處理寫入寫出操作和UI操作的情況下娇昙,真正起作用的是異步,而不是多線程笤妙∶罢疲可以不用多線程,但不能不用異步蹲盘。
GCD
GCD以block(Swift中是閉包股毫,為了方便,下面都以block表示)為基本單位召衔,一個(gè)block中的代碼可以為一個(gè)任務(wù)铃诬。下文中提到任務(wù),可以理解為執(zhí)行某個(gè)block薄嫡。
同時(shí)氧急,GCD中有兩大最重要的概念,分別是“隊(duì)列”和“執(zhí)行方式”毫深。
使用block的過(guò)程吩坝,概括來(lái)說(shuō)就是把block放進(jìn)合適的隊(duì)列,并選擇合適的執(zhí)行方式去執(zhí)行block的過(guò)程哑蔫。
三種隊(duì)列:
串行隊(duì)列(先進(jìn)入隊(duì)列的任務(wù)先出隊(duì)列钉寝,每次只執(zhí)行一個(gè)任務(wù))
并發(fā)隊(duì)列(依然是“先入先出”弧呐,不過(guò)可以形成多個(gè)任務(wù)并發(fā))
主隊(duì)列(這是一個(gè)特殊的串行隊(duì)列,而且隊(duì)列中的任務(wù)一定會(huì)在主線程中執(zhí)行)
兩種執(zhí)行方式:
同步執(zhí)行
異步執(zhí)行
關(guān)于同步/異步嵌纲、串行/并行和線程的關(guān)系俘枫,下面通過(guò)一個(gè)表格來(lái)總結(jié):
同步 | 異步 | |
---|---|---|
主隊(duì)列 | 在主線程中執(zhí)行 | 在主線程中執(zhí)行 |
串行隊(duì)列 | 在當(dāng)前線程中執(zhí)行 | 新建線程執(zhí)行 |
并發(fā)隊(duì)列 | 在當(dāng)前線程中執(zhí)行 | 新建線程執(zhí)行 |
可以看到,同步方法不一定在本線程逮走,因?yàn)榧尤氲街麝?duì)列的就會(huì)在主線程內(nèi)執(zhí)行鸠蚪;異步方法方法也不一定新開(kāi)線程,也是因?yàn)橹麝?duì)列的特殊情況师溅。
在我們的實(shí)際開(kāi)發(fā)過(guò)程中茅信,我們要更多考慮的是怎么準(zhǔn)確的使用好串行/并行、同步/異步墓臭,而不是僅僅只考慮是否新開(kāi)線程這個(gè)問(wèn)題蘸鲸。
當(dāng)然,了解任務(wù)運(yùn)行在那個(gè)線程中也是為了更加深入的理解整個(gè)程序的運(yùn)行情況窿锉,尤其是接下來(lái)要討論的死鎖問(wèn)題酌摇。
GCD的死鎖問(wèn)題
在使用GCD的過(guò)程中,如果向當(dāng)前串行隊(duì)列中同步派發(fā)一個(gè)任務(wù)嗡载,就會(huì)導(dǎo)致死鎖窑多。
這句話有點(diǎn)繞,先舉個(gè)例子看看:
override func viewDidLoad() {
super.viewDidLoad()
let queue = DispatchQueue.main
queue.sync {
print("啊哈哈")
}
}
這段代碼就會(huì)導(dǎo)致死鎖鼻疮,因?yàn)槲覀兡壳霸谥麝?duì)列中怯伊,又將要同步地添加一個(gè)block到主隊(duì)列中。
先分析一波
我們知道.sync
表示同步的執(zhí)行任務(wù)判沟,也就是說(shuō)執(zhí)行.sync
后耿芹,當(dāng)前線程會(huì)阻塞。而.sync
中的block如果要在當(dāng)前線程中執(zhí)行挪哄,就得等待當(dāng)前線程執(zhí)行完成吧秕。
在上面這個(gè)例子中,主線程在執(zhí)行.sync
迹炼,隨后主隊(duì)列中新增一個(gè)任務(wù)block砸彬。因?yàn)橹麝?duì)列是串行隊(duì)列,所以block要等.sync
執(zhí)行完才能執(zhí)行斯入,但是.sync
是同步派發(fā)砂碉,要等block執(zhí)行完才算是結(jié)束。在主隊(duì)列中的兩個(gè)任務(wù)互相等待刻两,導(dǎo)致了死鎖增蹭。
解決方案
其實(shí)在通常情況下我們不必要用.sync
,因?yàn)?code>.async能夠更好的利用CPU磅摹,提升程序運(yùn)行速度滋迈。只有當(dāng)我們需要保證隊(duì)列中的任務(wù)必須順序執(zhí)行時(shí)霎奢,才考慮.sync
。在使用.sync
的時(shí)候應(yīng)該分析當(dāng)前處于哪個(gè)隊(duì)列饼灿,以及任務(wù)會(huì)提交到哪個(gè)隊(duì)列幕侠。
DispatchGroup
在平時(shí)的開(kāi)發(fā)過(guò)程中,可能會(huì)有這樣的需求:我需要在完成一些任務(wù)之后緊接著去執(zhí)行另外一個(gè)任務(wù)碍彭,這里晤硕,我們就可以使用GCD任務(wù)組解決類似需求。
在單個(gè)串行隊(duì)列中硕旗,這個(gè)需求不是問(wèn)題窗骑,因?yàn)橹灰鸦卣{(diào)block添加到隊(duì)列末尾即可。
但是對(duì)于并行隊(duì)列漆枚,以及多個(gè)串行、并行隊(duì)列混合的情況抵知,就需要使用DispatchGroup
了墙基。
override func viewDidLoad() {
super.viewDidLoad()
let concurrentQueue = DispatchQueue(label: "concurrentQueue", qos: .default, attributes: .concurrent, autoreleaseFrequency: .workItem, target: nil)
let serialQueue = DispatchQueue(label: "serialQueue")
let group = DispatchGroup()
for i in 0...3 {
concurrentQueue.async(group: group, qos: .default, flags: []) {
print("concurrentQueue\(i)")
}
}
for i in 0...3 {
serialQueue.async(group: group, qos: .default, flags: []) {
print("serialQueue\(i)")
}
}
//執(zhí)行完上面的兩個(gè)耗時(shí)操作, 回到主隊(duì)列中執(zhí)行下一步的任務(wù)
group.notify(queue: DispatchQueue.main) {
print("回到主隊(duì)列執(zhí)行一些操作")
}
}
輸出:
concurrentQueue1
serialQueue0
concurrentQueue2
concurrentQueue0
concurrentQueue3
serialQueue1
serialQueue2
serialQueue3
回到主隊(duì)列執(zhí)行一些操作
首先創(chuàng)建一個(gè)并發(fā)隊(duì)列和串行隊(duì)列,然后通過(guò)DispatchGroup()
方法生成一個(gè)組刷喜。
接下來(lái)残制,在兩個(gè)不同的隊(duì)列里面分別加入不同的任務(wù),并放入到group
中去掖疮。
最后調(diào)用group.notify
方法初茶。這個(gè)方法表示把第二個(gè)參數(shù) block 傳入第一個(gè)參數(shù)隊(duì)列中去。而且可以保證第二個(gè)參數(shù) block 執(zhí)行時(shí)浊闪,group
中的所有任務(wù)已經(jīng)全部完成恼布。
.asyncAfter方法
通過(guò) GCD 還可以進(jìn)行簡(jiǎn)單的定時(shí)操作,比如在 1 秒后執(zhí)行某個(gè) block 搁宾。代碼如下:
DispatchQueue.main.asyncAfter(deadline:DispatchTime.now() + 1 ) {
print("我是在一秒后執(zhí)行的")
}
.asyncAfter
方法的調(diào)用者表示要執(zhí)行的任務(wù)提交到哪個(gè)隊(duì)列折汞,后面有兩個(gè)參數(shù)。第一個(gè)表示時(shí)間盖腿,也就是從現(xiàn)在起往后一秒鐘爽待。第二個(gè)參數(shù)分別表示要提交的任務(wù)。
需要注意的是.asyncAfter
僅表示在指定時(shí)間后提交任務(wù)翩腐,而非執(zhí)行任務(wù)鸟款。如果任務(wù)提交到主隊(duì)列,它將在main runloop
中執(zhí)行茂卦,對(duì)于每隔1/60
秒執(zhí)行一次的RunLoop何什,任務(wù)最多有可能在1+1/60
秒后執(zhí)行。
Operation
Operation
和OperationQueue
主要涉及這幾個(gè)方面:
-
Operation
和OperationQueue
用法介紹 -
Operation
的暫停疙筹、恢復(fù)和取消 - 通過(guò) KVO 對(duì)
Operation
的狀態(tài)進(jìn)行檢測(cè) - 多個(gè)
Operation
的之間的依賴關(guān)系
從簡(jiǎn)單意義上來(lái)說(shuō)富俄,Operation
是對(duì) GCD 中的 block 進(jìn)行的封裝禁炒,它也表示一個(gè)要被執(zhí)行的任務(wù),Operation
對(duì)象有一個(gè)start()
方法表示開(kāi)始執(zhí)行這個(gè)任務(wù)霍比。
不僅如此幕袱,Operation
表示的任務(wù)還可以被取消。它還有三種狀態(tài)isExecuted悠瞬、isFinished们豌、isCancelled
以方便我們通過(guò) KVC 對(duì)它的狀態(tài)進(jìn)行監(jiān)聽(tīng)。
想要開(kāi)始執(zhí)行一個(gè)任務(wù)可以這么寫:
let operation = BlockOperation.init {
print("初始化 0 的任務(wù)\(Thread.current)")
}
operation.addExecutionBlock {
print("第 1 個(gè)添加任務(wù)\(Thread.current)")
}
operation.addExecutionBlock {
print("第 2 個(gè)添加任務(wù)\(Thread.current)")
}
operation.addExecutionBlock {
print("第 3 個(gè)添加任務(wù)\(Thread.current)")
}
operation.addExecutionBlock {
print("第 4 個(gè)添加任務(wù)\(Thread.current)")
}
operation.addExecutionBlock {
print("第 5 個(gè)添加任務(wù)\(Thread.current)")
}
operation.addExecutionBlock {
print("第 6 個(gè)添加任務(wù)\(Thread.current)")
}
operation.addExecutionBlock {
print("第 7 個(gè)添加任務(wù)\(Thread.current)")
}
operation.addExecutionBlock {
print("第 8 個(gè)添加任務(wù)\(Thread.current)")
}
operation.start()
print("結(jié)束了")
輸出內(nèi)容:
初始化 0 的任務(wù)<NSThread: 0x60000007dc40>{number = 1, name = main}
第 2 個(gè)添加任務(wù)<NSThread: 0x60000026af80>{number = 3, name = (null)}
第 1 個(gè)添加任務(wù)<NSThread: 0x608000262e80>{number = 5, name = (null)}
第 3 個(gè)添加任務(wù)<NSThread: 0x608000262d80>{number = 4, name = (null)}
第 4 個(gè)添加任務(wù)<NSThread: 0x60000007dc40>{number = 1, name = main}
第 5 個(gè)添加任務(wù)<NSThread: 0x60000026af80>{number = 3, name = (null)}
第 6 個(gè)添加任務(wù)<NSThread: 0x608000262e80>{number = 5, name = (null)}
第 8 個(gè)添加任務(wù)<NSThread: 0x60000007dc40>{number = 1, name = main}
第 7 個(gè)添加任務(wù)<NSThread: 0x608000262d80>{number = 4, name = (null)}
結(jié)束了
使用BlockOperation
來(lái)創(chuàng)建是因?yàn)?code>Operation是一個(gè)基類浅妆,不應(yīng)該直接生成Operation
對(duì)象望迎,而是應(yīng)該用它的子類。BlockOperation
是蘋果預(yù)定義的子類凌外,它可以用來(lái)封裝一個(gè)或多個(gè) block 辩尊,后面會(huì)介紹如何自己創(chuàng)建Operation
的子類。
在上面的例子里面我們創(chuàng)建了一個(gè)BlockOperation
康辑,并且設(shè)置好它的 block 摄欲,也就是將要執(zhí)行的任務(wù),同時(shí)疮薇,我們調(diào)用addExecutionBlock
方法追加幾個(gè)任務(wù)胸墙,這些任務(wù)會(huì)并行執(zhí)行。但是它并非是將所有的 block 都放到放到了子線程中按咒。通過(guò)上面的打印記錄我們可以發(fā)現(xiàn)迟隅,它會(huì)優(yōu)先將 block 放到主線程中執(zhí)行,若主線程已有待執(zhí)行的代碼励七,就開(kāi)辟新的線程智袭,但最大并發(fā)數(shù)為4(包括主線程在內(nèi),在真機(jī)上最大并發(fā)數(shù)為2呀伙,不必糾結(jié)這個(gè)补履,明白原理即可),如果 block 數(shù)量大于了線程的最大并發(fā)數(shù)剿另,那么剩下的 block 就會(huì)等待某個(gè)線程空閑下來(lái)之后被分配到該線程箫锤,且依然是優(yōu)先分配到主線程。
最后雨女,調(diào)用start()
方法讓Operation
方法運(yùn)行起來(lái)谚攒。start()
是一個(gè)同步方法,也就是在調(diào)用start()
方法的那個(gè)線程中直接執(zhí)行氛堕,會(huì)阻塞調(diào)用start()
方法的線程馏臭。
OperationQueue
從上面我們可以知道Operation
是同步執(zhí)行的。簡(jiǎn)單的看一下 NSOperation 類的定義會(huì)發(fā)現(xiàn)它有一個(gè)只讀屬性 asynchronous,這意味著如果想要異步執(zhí)行括儒,就需要自定Operation的
子類绕沈。或者使用OperationQueue
帮寻。
OperationQueue
類似于 GCD 中的隊(duì)列乍狐。我們知道 GCD 中的隊(duì)列有三種:主隊(duì)列、串行隊(duì)列和并行隊(duì)列固逗。OperationQueue
更簡(jiǎn)單浅蚪,只有兩種:主隊(duì)列和非主隊(duì)列。我們自己生成的OperationQueue
對(duì)象都是非主隊(duì)列烫罩,主隊(duì)列可以用OperationQueue.main
取得惜傲。
OperationQueue
的主隊(duì)列是串行隊(duì)列,而且其中所有Operation
都會(huì)在主線程中執(zhí)行贝攒。對(duì)于非主隊(duì)列來(lái)說(shuō)盗誊,一旦一個(gè)Operation
被放入其中,那這個(gè)Operation
一定是并發(fā)執(zhí)行的饿这。因?yàn)?code>OperationQueue會(huì)為每一個(gè)Operation
創(chuàng)建線程并調(diào)用它的start()
方法浊伙。
OperationQueue
有一個(gè)屬性叫maxConcurrentOperationCount
,它表示最多支持多少個(gè)Operation
并發(fā)執(zhí)行长捧。如果maxConcurrentOperationCount
被設(shè)為 1,就認(rèn)為這個(gè)隊(duì)列是串行隊(duì)列吻贿。需要注意的是設(shè)備最大并發(fā)數(shù)是有上限的串结,即使你設(shè)置maxConcurrentOperationCount
為100,它也不會(huì)超過(guò)設(shè)備最大并發(fā)數(shù)上限舅列,而這個(gè)上限的數(shù)目也是由具體運(yùn)行環(huán)境決定的肌割。
OperationQueue
和GCD
中的隊(duì)列有這樣的對(duì)應(yīng)關(guān)系:
OperationQueue | GCD | |
---|---|---|
主隊(duì)列 | OperationQueue.main |
DispatchQueue.main |
串行隊(duì)列 | 自建隊(duì)列設(shè)置maxConcurrentOperationCount 為 1 |
DispatchQueue(label: "serialQueue") |
并發(fā)隊(duì)列 | 自建隊(duì)列設(shè)置maxConcurrentOperationCount 大于 1 |
DispatchQueue(label: "concurrentQueue", qos: .default, attributes: .concurrent, autoreleaseFrequency: .workItem, target: nil |
想要使用OperationQueue
實(shí)現(xiàn)異步操作可以這么寫:
let operationQueue = OperationQueue()
let operation = BlockOperation()
for i in 1...10 {
operation.addExecutionBlock {
print("第 \(i) 個(gè)添加任務(wù)\(Thread.current)")
}
}
operationQueue.addOperation(operation)
輸出內(nèi)容:
第 1 個(gè)添加任務(wù)<NSThread: 0x1702619c0>{number = 5, name = (null)}
第 2 個(gè)添加任務(wù)<NSThread: 0x170261880>{number = 4, name = (null)}
第 3 個(gè)添加任務(wù)<NSThread: 0x1702619c0>{number = 5, name = (null)}
第 4 個(gè)添加任務(wù)<NSThread: 0x170261880>{number = 4, name = (null)}
第 5 個(gè)添加任務(wù)<NSThread: 0x1702619c0>{number = 5, name = (null)}
第 6 個(gè)添加任務(wù)<NSThread: 0x170261880>{number = 4, name = (null)}
第 7 個(gè)添加任務(wù)<NSThread: 0x1702619c0>{number = 5, name = (null)}
第 8 個(gè)添加任務(wù)<NSThread: 0x170261880>{number = 4, name = (null)}
第 9 個(gè)添加任務(wù)<NSThread: 0x1702619c0>{number = 5, name = (null)}
第 10 個(gè)添加任務(wù)<NSThread: 0x170261880>{number = 4, name = (null)}
使用OperationQueue
來(lái)執(zhí)行任務(wù)與之前的區(qū)別在于,首先創(chuàng)建一個(gè)非主隊(duì)列帐要。然后用addOperation
方法替換之前的start()
方法把敞。剛剛已經(jīng)說(shuō)過(guò),OperationQueue
會(huì)為每一個(gè)Operation
建立線程并調(diào)用他們的start()
方法榨惠。
觀察一下運(yùn)行結(jié)果奋早,所有的Operation
都沒(méi)有在主線程執(zhí)行,從而成功的實(shí)現(xiàn)了異步赠橙、并行處理耽装。
除了上述的將Operation
添加到隊(duì)列中的使用方法外,OperationQueue
提供了一個(gè)更加簡(jiǎn)單的方法期揪,只需以下兩行代碼就能實(shí)現(xiàn)多線程調(diào)用
let operationQueue = OperationQueue()
operationQueue.addOperation {
print(Thread.current)
}
輸出內(nèi)容:
<NSThread: 0x170460d40>{number = 4, name = (null)}
你可以同時(shí)添加一個(gè)或這個(gè)多個(gè)Block來(lái)實(shí)現(xiàn)你的操作掉奄。
取消任務(wù)
如果我們有兩次網(wǎng)絡(luò)請(qǐng)求,第二次請(qǐng)求會(huì)用到第一次的數(shù)據(jù)凤薛。假設(shè)此時(shí)網(wǎng)絡(luò)情況不好姓建,第一次請(qǐng)求超時(shí)了诞仓,那么第二次請(qǐng)求也沒(méi)有必要發(fā)送了。而且用戶也有可能人為地取消某個(gè)Operation
速兔。
當(dāng)產(chǎn)生這種需求的時(shí)候墅拭,我們就可以取消這些操作:
//取消Operation
let operation = BlockOperation.init {
print("哈哈哈哈哈哈 0")
}
operation.cancel()
//取消某個(gè)OperationQueue剩余的Operation
let operationQueue = OperationQueue()
for i in 1...10 {
operationQueue.addOperation {
print(Thread.current)
}
}
operationQueue.cancelAllOperations()
暫停和取消并不會(huì)立即暫停或取消當(dāng)前操作憨栽,而是不在調(diào)用新的Operation
帜矾。
設(shè)置依賴
如果現(xiàn)在需要兩次網(wǎng)絡(luò)請(qǐng)求,第二次請(qǐng)求會(huì)用到第一次的數(shù)據(jù)屑柔,所以我們要保證發(fā)出第二次請(qǐng)求的時(shí)候第一個(gè)請(qǐng)求已經(jīng)執(zhí)行完屡萤,但是我們同時(shí)還希望利用到OperationQueue
的并發(fā)特性(因?yàn)榭赡懿恢惯@兩個(gè)任務(wù))。
這時(shí)候我們可以設(shè)置Operation
之間的依賴關(guān)系:
//讓operation1在operation2執(zhí)行完之后執(zhí)行
let operationQueue = OperationQueue()
let operation1 = BlockOperation.init {
print("第 1 個(gè)添加任務(wù)\(Thread.current)")
}
let operation2 = BlockOperation.init {
print("第 2 個(gè)添加任務(wù)\(Thread.current)")
}
//需要注意的是Operation之間的相互依賴會(huì)導(dǎo)致死鎖掸宛,如果1依賴2死陆,2又依賴1,就會(huì)導(dǎo)致死鎖唧瘾。
operation1.addDependency(operation2)
operationQueue.addOperation(operation1)
operationQueue.addOperation(operation2)
輸入內(nèi)容:
第 2 個(gè)添加任務(wù)<NSThread: 0x17027a340>{number = 4, name = (null)}
第 1 個(gè)添加任務(wù)<NSThread: 0x17027a340>{number = 4, name = (null)}
OperationQueue暫停與恢復(fù)
暫停與恢復(fù)只需要操作isSuspended
屬性:
operationQueue.isSuspended = true
operationQueue.isSuspended = false
Operation優(yōu)先級(jí)
每一個(gè)Operation
的對(duì)象都一個(gè)queuePriority
屬性措译,表示隊(duì)列優(yōu)先級(jí)。它是一個(gè)枚舉值饰序,有這么幾個(gè)等級(jí)可選:
public enum QueuePriority : Int {
case veryLow
case low
case normal
case high
case veryHigh
}
需要注意的是领虹,這個(gè)優(yōu)先級(jí)并不總是起作用,不能完全保證優(yōu)先級(jí)高的任務(wù)一定先執(zhí)行求豫,因?yàn)榫€程優(yōu)先級(jí)代表的是線程獲取CPU時(shí)間片的能力塌衰,高優(yōu)先級(jí)的執(zhí)行概率高,但是并不能確保優(yōu)先級(jí)高的一定先執(zhí)行蝠嘉。