多線程之GCD與NSOperation

開(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

OperationOperationQueue主要涉及這幾個(gè)方面:

  • OperationOperationQueue用法介紹
  • 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)境決定的肌割。

OperationQueueGCD中的隊(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í)行蝠嘉。

參考:iOS多線程編程總結(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末最疆,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蚤告,更是在濱河造成了極大的恐慌努酸,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件杜恰,死亡現(xiàn)場(chǎng)離奇詭異获诈,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)箫章,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門烙荷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人檬寂,你說(shuō)我怎么就攤上這事终抽。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵昼伴,是天一觀的道長(zhǎng)匾旭。 經(jīng)常有香客問(wèn)我,道長(zhǎng)圃郊,這世上最難降的妖魔是什么价涝? 我笑而不...
    開(kāi)封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮持舆,結(jié)果婚禮上色瘩,老公的妹妹穿的比我還像新娘。我一直安慰自己逸寓,他們只是感情好居兆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著竹伸,像睡著了一般泥栖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上勋篓,一...
    開(kāi)封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天吧享,我揣著相機(jī)與錄音,去河邊找鬼譬嚣。 笑死钢颂,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的拜银。 我是一名探鬼主播甸陌,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼盐股!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起耻卡,我...
    開(kāi)封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤疯汁,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后卵酪,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體幌蚊,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年溃卡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了溢豆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瘸羡,死狀恐怖漩仙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤队他,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布卷仑,位于F島的核電站,受9級(jí)特大地震影響麸折,放射性物質(zhì)發(fā)生泄漏锡凝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一垢啼、第九天 我趴在偏房一處隱蔽的房頂上張望窜锯。 院中可真熱鬧,春花似錦芭析、人聲如沸锚扎。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)工秩。三九已至,卻和暖如春进统,著一層夾襖步出監(jiān)牢的瞬間助币,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工螟碎, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留眉菱,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓掉分,卻偏偏與公主長(zhǎng)得像俭缓,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子酥郭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容