本文將會(huì)從多個(gè)方面探討NSOperation類和NSOperationQueue類的相關(guān)內(nèi)容
一硬耍、簡(jiǎn)介
NSOperation類是iOS2.0推出的拖叙,通過(guò)NSThread實(shí)現(xiàn)的,但是效率一般凤粗。
從OS X10.6和iOS4推出GCD時(shí)黍判,又重寫(xiě)了NSOperation和NSOperationQueue庙楚,NSOperation和NSOperationQueue分別對(duì)應(yīng)GCD的任務(wù)和隊(duì)列失受,所以NSOPeration和NSOperationQueue是基于GCD更高一層的封裝,而且完全地面向?qū)ο笤帷5潜菺CD更簡(jiǎn)單易用檐涝、代碼可讀性也更高。NSOperation和NSOperationQueue對(duì)比GCD會(huì)帶來(lái)一點(diǎn)額外的系統(tǒng)開(kāi)銷法挨,但是可以在多個(gè)操作Operation中添加附屬谁榜。
二、知識(shí)概括
從NSOperation的思維導(dǎo)圖了解的這個(gè)類相關(guān)的整體的知識(shí)點(diǎn):
NSOperation和NSOperationQueue是基于GCD的更高一層的封裝凡纳,分別對(duì)應(yīng)GCD的任務(wù)和隊(duì)列窃植,完全地面向?qū)ο蟆荐糜?梢酝ㄟ^(guò)start
方法直接啟動(dòng)NSOperation子類對(duì)象巷怜,并且默認(rèn)同步執(zhí)行任務(wù),將NSOperation子類對(duì)象添加到NSOperationQueue中暴氏,該隊(duì)列默認(rèn)并發(fā)的調(diào)度任務(wù)延塑。
開(kāi)啟操作有二種方式,一是通過(guò)start方法直接啟動(dòng)操作答渔,該操作默認(rèn)同步執(zhí)行关带,二是將操作添加到NSOperationQueue中,然后由系統(tǒng)從隊(duì)列中獲取操作然后添加到一個(gè)新線程中執(zhí)行沼撕,這些操作默認(rèn)并發(fā)執(zhí)行宋雏。
具體實(shí)現(xiàn)如下:
方式一:直接由NSOperation子類對(duì)象啟動(dòng)。
首先將需要執(zhí)行的操作封裝到NSOperation子類對(duì)象中务豺,然后該對(duì)象調(diào)用Start方法磨总。
方式二:當(dāng)添加到NSOperationQueue對(duì)象中,由該隊(duì)列對(duì)象啟動(dòng)操作笼沥。
- 將需要執(zhí)行的操作封裝到NSOperation子類對(duì)象中
- 將該對(duì)象添加到NSOperationQueue中
- 系統(tǒng)將NSOperation子類對(duì)象從NSOperationQueue中取出
- 將取出的操作放到一個(gè)新線程中執(zhí)行
使用隊(duì)列來(lái)執(zhí)行操作蚪燕,分為2個(gè)階段:第一階段:添加到線程隊(duì)列的過(guò)程招狸,是上圖的步驟1和2。第二階段:系統(tǒng)自動(dòng)從隊(duì)列中取出線程邻薯,并且自動(dòng)放到線程中執(zhí)行,是上圖的步驟3和4乘凸。
接下來(lái)相關(guān)內(nèi)容的總結(jié):
1. NSOperation
NSOperation是一個(gè)和任務(wù)相關(guān)的抽象類厕诡,不具備封裝操作的能力,必須使用其子類营勤。
使用NSOperation?類的方式有3種:
- 系統(tǒng)實(shí)現(xiàn)的具體子類:NSInvocationOperation
- 系統(tǒng)實(shí)現(xiàn)的具體子類:NSBlockOperation
- 自定義子類,實(shí)現(xiàn)內(nèi)部相應(yīng)的?法
該類是線程安全的灵嫌,不必管理線程生命周期和同步等問(wèn)題。
a. NSInvocationOperation子類
NSInvocationOperation是NSOperation的子類葛作。創(chuàng)建操作對(duì)象的方式有2種寿羞,使用initWithTarget:selector:object:
創(chuàng)建sel參數(shù)是一個(gè)或0個(gè)的操作對(duì)象。使用initWithInvocation:
方法赂蠢,添加sel參數(shù)是0個(gè)或多個(gè)操作對(duì)象绪穆。在未添加到隊(duì)列的情況下,創(chuàng)建操作對(duì)象的過(guò)程中不會(huì)開(kāi)辟線程虱岂,會(huì)在當(dāng)前線程中執(zhí)行同步操作玖院。創(chuàng)建完成后,直接調(diào)用start方法第岖,會(huì)啟動(dòng)操作對(duì)象來(lái)執(zhí)行操难菌,或者添加到NSOperationQueue隊(duì)列中。無(wú)論使用該子類的哪個(gè)在初始化的方法蔑滓,都會(huì)在添加一個(gè)任務(wù)郊酒。 和NSBlockOperation子類不同的是,因?yàn)闆](méi)有額外添加任務(wù)的方法键袱,使用NSInvocationOperation創(chuàng)建的對(duì)象只會(huì)有一個(gè)任務(wù)燎窘。
默認(rèn)情況下,調(diào)用start方法不會(huì)開(kāi)辟一個(gè)新線程去執(zhí)行操作杠纵,而是在當(dāng)前線程同步執(zhí)行任務(wù)仙畦。只有將其放到一個(gè)NSOperationQueue中,才會(huì)異步執(zhí)行操作
b. NSBlockOperation子類
可以通過(guò)blockOperationWithBlock:
創(chuàng)建NSBlockOperation對(duì)象,在創(chuàng)建的時(shí)候也添加一個(gè)任務(wù)泛啸。如果想添加更多的任務(wù)贡这,可以使用addExecutionBlock:
方法。也可以通過(guò)init:創(chuàng)建NSBlockOperation對(duì)象银亲。但是這種創(chuàng)建方式并不會(huì)在創(chuàng)建對(duì)象的時(shí)候添加任務(wù)慢叨,同樣可以使用addExecutionBlock:
方法添加任務(wù)。對(duì)于啟動(dòng)操作和NSInvocationOperation類一樣务蝠,都可以通過(guò)調(diào)用start方法和添加NSOperationQueue中來(lái)執(zhí)行操作拍谐。
關(guān)于任務(wù)的的同步、異步的執(zhí)行可以總結(jié)幾點(diǎn):
- 任務(wù)數(shù)為1時(shí),即使用
blockOperationWithBlock:
方法或者init:
與addExecutionBlock:
二個(gè)方法結(jié)合的方式創(chuàng)建的唯一一個(gè)任務(wù)時(shí)轩拨,不會(huì)開(kāi)辟新線程践瓷,直接在當(dāng)前線程同步執(zhí)行任務(wù)。 - 任務(wù)數(shù)大于1時(shí)亡蓉,使用
blockOperationWithBlock:
方法或者init:
與addExecutionBlock:
二個(gè)方法結(jié)合的方式創(chuàng)建的一個(gè)任務(wù)A晕翠,不會(huì)開(kāi)辟線程,直接在當(dāng)前線程同步執(zhí)行任務(wù)砍濒。而NSBlockOperation對(duì)象使用addExecutionBlock:
方法添加的其他任務(wù)會(huì)開(kāi)辟新線程淋肾,異步執(zhí)行任務(wù)。 - 將操作放到一個(gè)NSOperationQueue中爸邢,會(huì)異步執(zhí)行操作任務(wù)樊卓。
注意:不可在completionBlock屬性的block中追加任務(wù),因?yàn)樵诓僮饕呀?jīng)啟動(dòng)執(zhí)行中或者結(jié)束后不可以添加block任務(wù)杠河。
c. 自定義子類
一般類NSInvocationOperation碌尔、NSBlockOperation就可以滿足使用需要,當(dāng)然還可以自己自定義子類券敌。
創(chuàng)建的子類時(shí)七扰,需要考慮到可能會(huì)添加到串行和并發(fā)隊(duì)列的不同情況,需要重寫(xiě)不同的方法陪白。對(duì)于串行操作颈走,僅僅需要重新main方法就行,在這個(gè)方法中添加想要實(shí)現(xiàn)的功能咱士。對(duì)于并發(fā)操作立由,重寫(xiě)四個(gè)方法:start
、asynchronous
序厉、executing
锐膜、finished
。并且需要自己創(chuàng)建自動(dòng)釋放池弛房,因?yàn)楫惒讲僮鳠o(wú)法訪問(wèn)主線程的自動(dòng)釋放池道盏。
注意:在自定義子類時(shí),經(jīng)常通過(guò)cancelled屬性檢查方法是否取消文捶,并且對(duì)取消的做出響應(yīng)荷逞。
2. NSOperationQueue
使用將NSOperation對(duì)象添加NSOperationQueue中,來(lái)管理操作對(duì)象是非常方便的粹排。因?yàn)楫?dāng)我們把操作對(duì)象添加到NSOperationQueue對(duì)象后种远,該NSOperationQueue對(duì)象從線程中拿取操作、以及分配到對(duì)應(yīng)線程的工作都是由系統(tǒng)處理的顽耳。
只要是創(chuàng)建了隊(duì)列坠敷,在隊(duì)列中的操作妙同,就會(huì)在子線程中執(zhí)行,并且默認(rèn)并發(fā)操作膝迎。添加到子隊(duì)列NSOperationQueue實(shí)例中的操作粥帚,都是異步執(zhí)行
a.操作對(duì)象添加到NSOperationQueue對(duì)象中
添加的方式有3種。
-
addOperation:
添加一個(gè)操作 -
addOperationWithBlock:
限次,系統(tǒng)自動(dòng)封裝成一個(gè)NSBlockOperation對(duì)象茎辐,然后添加到隊(duì)列中 -
addOperations:waitUntilFinished:
添加多個(gè)操作
操作對(duì)象添加到NSOperationQueue之后,通常短時(shí)間內(nèi)就會(huì)運(yùn)行。但是如果存在依賴,或者整個(gè)隊(duì)列被暫停等原因,也可能需要等待掂恕。
操作對(duì)象添加NSOperationQueue中后,不要再修改操作對(duì)象的狀態(tài)弛槐。因?yàn)椴僮鲗?duì)象可能會(huì)在任何時(shí)候運(yùn)行,因此改變操作對(duì)象的依賴或數(shù)據(jù)會(huì)產(chǎn)生無(wú)法預(yù)估的問(wèn)題懊亡。只能查看操作對(duì)象的狀態(tài), 比如是否正在運(yùn)行、等待運(yùn)行乎串、已經(jīng)完成等店枣。
b. 設(shè)置最多并發(fā)數(shù)
雖然NSOperationQueue類設(shè)計(jì)用于并發(fā)執(zhí)行操作,但是也可以強(qiáng)制讓單個(gè)隊(duì)列一次只能調(diào)度一個(gè)操作對(duì)象叹誉。setMaxConcurrentOperationCount:方法可以設(shè)置隊(duì)列的最大并發(fā)操作數(shù)量鸯两。當(dāng)設(shè)為1就表示NSOperationQueue實(shí)例每次只能執(zhí)行一個(gè)NSOperation子類對(duì)象。不過(guò)操作對(duì)象執(zhí)行的順序會(huì)依賴于其它因素,比如操作是否準(zhǔn)備好和操作對(duì)象的優(yōu)先級(jí)等长豁。因此串行化的operation queue并不等同于GCD中的串行dispatch queue钧唐。
maxConcurrentOperationCount默認(rèn)是-1,不可設(shè)置為0匠襟。如果沒(méi)有設(shè)置最大并發(fā)數(shù)钝侠,那么并發(fā)的個(gè)數(shù)是由系統(tǒng)內(nèi)存和CPU決定的。
相關(guān)概念:
- 并發(fā)數(shù): NSOperationQueue隊(duì)列里同時(shí)能調(diào)度的NSOperation對(duì)象數(shù)酸舍。
- 最大并發(fā)數(shù): 同一時(shí)間最多能調(diào)度的NSOperation對(duì)象數(shù)帅韧。
c. 進(jìn)度修改
一個(gè)操作執(zhí)行還未完成時(shí),我們可能需要讓該任務(wù)暫停啃勉、可能之后在進(jìn)行某些操作后又希望繼續(xù)執(zhí)行忽舟。為了滿足這個(gè)需要,蘋果公司淮阐,為我們提供了suspended屬性叮阅。當(dāng)可能我們不想執(zhí)行某些操作時(shí),可以個(gè)cancel
方法泣特、cancelAllOperations
方法可以取消操作對(duì)象帘饶,一旦調(diào)用了這2個(gè)方法,操作對(duì)象將無(wú)法恢復(fù)群扶。具體如下:
對(duì)于暫停操作及刻,當(dāng)NSOperationQueue對(duì)象屬性suspended設(shè)置為YES镀裤,隊(duì)列會(huì)停止對(duì)任務(wù)調(diào)度。對(duì)那些還在線程中的操作有影響的缴饭。如果任務(wù)正在執(zhí)行將不會(huì)受到影響暑劝,因?yàn)槿蝿?wù)已經(jīng)被隊(duì)列調(diào)度到一個(gè)線程上并執(zhí)行。
對(duì)于繼續(xù)操作颗搂,當(dāng)屬性suspended設(shè)置為NO會(huì)繼續(xù)執(zhí)行線程操作担猛。隊(duì)列將積極啟動(dòng)隊(duì)列中已準(zhǔn)備執(zhí)行的操作。
一旦NSOperation子類操作對(duì)象添加到NSOperationQueue對(duì)象中,該隊(duì)列就擁有了該操作對(duì)象并且不能刪除操作對(duì)象,如果不想執(zhí)行操作對(duì)象丢氢,只能取消該操作對(duì)象傅联。關(guān)于取消操作,可以分為2種情況疚察,取消一個(gè)操作和取消一個(gè)隊(duì)列的全部操作二種情況蒸走。調(diào)用NSOperation類實(shí)例的cancel
方法取消單個(gè)操作對(duì)象。調(diào)用NSOperationQueue類實(shí)例cancelAllOperations
方法取消隊(duì)列中全部操作對(duì)象貌嫡。
對(duì)于隊(duì)列中的操作比驻,只有操作標(biāo)記為已結(jié)束才能被隊(duì)列移除。在隊(duì)列中未被調(diào)度的操作岛抄,會(huì)調(diào)用start方法執(zhí)行操作别惦,以便操作對(duì)象處理取消事件。然后標(biāo)記這些操作對(duì)象為已結(jié)束夫椭。對(duì)于正在線程中執(zhí)行其任務(wù)的操作對(duì)象掸掸,正在執(zhí)行的任務(wù)會(huì)繼續(xù)執(zhí)行,該操作對(duì)象會(huì)被標(biāo)記經(jīng)結(jié)束蹭秋。
注意:只會(huì)停止調(diào)度隊(duì)列中操作對(duì)象猾漫,正在執(zhí)行任務(wù)的依然會(huì)執(zhí)行,且取消不可恢復(fù)感凤。
d.作用
NSOperation對(duì)象可以調(diào)?start?法來(lái)執(zhí)?任務(wù),但默認(rèn)是同步執(zhí)行的(可以創(chuàng)建異步操作悯周,NSBlockOperation添加操作數(shù)大于1時(shí),除第一個(gè)任務(wù)外陪竿,其任務(wù)就是異步執(zhí)行)禽翼。如果將NSOperation添加到NSOperationQueue中,之后操作就就由系統(tǒng)管理族跛,系統(tǒng)先從隊(duì)列中取出操作闰挡,然后放到一個(gè)新線程中異步執(zhí)行〗负澹總結(jié):添加操作到NSOperationQueue中长酗,自動(dòng)執(zhí)行操作,自動(dòng)開(kāi)啟線程
f. 獲取隊(duì)列
系統(tǒng)提供了2個(gè)桐绒,可以獲取當(dāng)前隊(duì)列和主隊(duì)列夺脾≈Γ可以通過(guò)類屬性currentQueue獲取當(dāng)前隊(duì)列∵职龋可以通過(guò)類屬性mainQueue獲取主隊(duì)列.
3.依賴
操作對(duì)象可以添加和移除依賴蚀乔。當(dāng)一個(gè)操作對(duì)象添加了依賴,被依賴的操作對(duì)象就會(huì)先執(zhí)行菲茬,當(dāng)被依賴的操作對(duì)象執(zhí)行完才會(huì)當(dāng)前的操作對(duì)象吉挣。添加到不同線程對(duì)象中的操作對(duì)象依然彼此間可以單方面依賴。切記循環(huán)依賴的情況婉弹。這樣會(huì)產(chǎn)生死循環(huán)睬魂。
可以通過(guò)addDependency方法添加一個(gè)或者多個(gè)依賴的對(duì)象。eg:[A addDependency:B];
操作A依賴于操作B镀赌。操作對(duì)象會(huì)管理自己的依賴氯哮,因此在不相同隊(duì)列中的操作對(duì)象可以建立依賴關(guān)系。但是一定要在添加線程對(duì)象NSOperationQueue之前佩脊,進(jìn)行依賴設(shè)置。設(shè)置依賴可以保證執(zhí)行順序垫卤,操作添加到隊(duì)列添加的順序并不能決定執(zhí)行順序威彰,執(zhí)行的順序取決于多種因素比如依賴、優(yōu)先級(jí)等穴肘。
調(diào)用removeDependency:方法移除依賴歇盼。
如圖,箭頭方向就是依賴的對(duì)象评抚,從圖中可知豹缀,A依賴b,而b依賴C慨代。所以三者的執(zhí)行順序是C-->b-->A
4.線程安全
在NSOperation實(shí)例在多線程上執(zhí)行是安全的邢笙,不需要添加額外的鎖
5.cancel方法
只會(huì)對(duì)未執(zhí)行的操作有效,正在執(zhí)行的操作侍匙,在收到cancel消息后氮惯,依然會(huì)執(zhí)行。
調(diào)用操作隊(duì)列中的操作的cancel方法想暗,且該操作隊(duì)列具有未完成的依賴操作時(shí)妇汗,那么這些依賴操作會(huì)被忽略。由于操作已經(jīng)被取消说莫,因此此行為允許隊(duì)列調(diào)用操作的start方法杨箭,以便在不調(diào)用其主方法的情況下從隊(duì)列中刪除操作。如果對(duì)不在隊(duì)列中的操作調(diào)用cancel方法储狭,則該操作立即標(biāo)記為已取消互婿。
6.狀態(tài)屬性
一個(gè)線程有未創(chuàng)建捣郊、就緒、運(yùn)行中擒悬、阻塞模她、消亡等多個(gè)狀態(tài)。而操作對(duì)象也有多種狀態(tài):executing(執(zhí)行中)懂牧、finished(完成)侈净、ready(就緒)狀態(tài),這三個(gè)屬性是蘋果公司僧凤,提供給我們用于觀察操作對(duì)象狀態(tài)的時(shí)候用的畜侦。因?yàn)檫@個(gè)三個(gè)屬性KVC與KVO兼容的,因此可以監(jiān)聽(tīng)操作對(duì)象狀態(tài)屬性。
7.操作完成
a. 監(jiān)聽(tīng)操作完成
當(dāng)我們可能需要在某個(gè)操作對(duì)象完成后添加一些功能躯保,此時(shí)就可以用屬性completionBlock來(lái)添加額外的內(nèi)容了旋膳。
operation.completionBlock = ^{
// 完成操作后,可以追加的內(nèi)容
};
b. 等待操作完成
這個(gè)有2種情況:一是等待單個(gè)操作對(duì)象途事,而是等待隊(duì)列里全部的操作验懊。
如果想等待整個(gè)隊(duì)列的操作,可以同時(shí)等待一個(gè)queue中的所有操作尸变。使用NSOperationQueue的waitUntilAllOperationsAreFinished
方法义图。在等待一個(gè)隊(duì)列時(shí),應(yīng)用的其它線程仍然可以往隊(duì)列中添加操作,因此可能會(huì)加長(zhǎng)線程的等待時(shí)間。
// 阻塞當(dāng)前線程召烂,等待queue的所有操作執(zhí)行完畢
[queue waitUntilAllOperationsAreFinished];
對(duì)于單個(gè)操作對(duì)象碱工,為了最佳的性能,盡可能設(shè)計(jì)異步操作,這樣可以讓?xiě)?yīng)用在正在執(zhí)行操作時(shí)可以去處理其它事情。如果需要當(dāng)前線程操作對(duì)象處理完成后的結(jié)果,可以使用NSOperation的waitUntilFinished
方法阻塞當(dāng)前線程奏夫,等待操作完成怕篷。通常應(yīng)該避免這樣編寫(xiě),阻塞當(dāng)前線程可能是一種簡(jiǎn)便的解決方案,但是它引入了更多的串行代碼,限制了整個(gè)應(yīng)用的并發(fā)性,同時(shí)也降低了用戶體驗(yàn)。絕對(duì)不要在應(yīng)用主線程中等待一個(gè)Operation,只能在非中等待酗昼。因?yàn)樽枞骶€程將導(dǎo)致應(yīng)用無(wú)法響應(yīng)用戶事件,應(yīng)用也將表現(xiàn)為無(wú)響應(yīng)廊谓。
// 會(huì)阻塞當(dāng)前線程,等到某個(gè)operation執(zhí)行完畢
[operation waitUntilFinished];
8.執(zhí)行順序
添加到NSOperationQueue中的操作對(duì)象麻削,其執(zhí)行順序取決于2點(diǎn):
1.首先判斷操作對(duì)象是否已經(jīng)準(zhǔn)備好:由對(duì)象的依賴關(guān)系確定
2.然后再根據(jù)所有操作對(duì)象的相對(duì)優(yōu)先級(jí)來(lái)確定:優(yōu)先級(jí)等級(jí)則是操作對(duì)象本身的一個(gè)屬性蹂析。默認(rèn)所有操作對(duì)象都擁有“普通”優(yōu)先級(jí),不過(guò)可以通過(guò)qualityOfService:方法來(lái)提升或降低操作對(duì)象的優(yōu)先級(jí)。優(yōu)先級(jí)只能應(yīng)用于相同隊(duì)列中的操作對(duì)象碟婆。如果應(yīng)用有多個(gè)操作隊(duì)列,每個(gè)隊(duì)列的優(yōu)先級(jí)等級(jí)是互相獨(dú)立的电抚。因此不同隊(duì)列中的低優(yōu)先級(jí)操作仍然可能比高優(yōu)先級(jí)操作更早執(zhí)行。
對(duì)于優(yōu)先級(jí)竖共,我們可以使用屬性queuePriority給某個(gè)操作對(duì)象設(shè)置高底蝙叛,優(yōu)先級(jí)高的任務(wù),調(diào)用的幾率會(huì)更大, 并不能保證執(zhí)行順序公给。并且優(yōu)先級(jí)不能替代依賴關(guān)系,優(yōu)先級(jí)只是對(duì)已經(jīng)準(zhǔn)備好的操作對(duì)象確定執(zhí)行順序借帘。先滿足依賴關(guān)系,然后再根據(jù)優(yōu)先級(jí)從所有準(zhǔn)備好的操作中選擇優(yōu)先級(jí)最高的那個(gè)執(zhí)行蜘渣。
9.服務(wù)質(zhì)量
根據(jù)CPU,網(wǎng)絡(luò)和磁盤的分配來(lái)創(chuàng)建一個(gè)操作的系統(tǒng)優(yōu)先級(jí)肺然。一個(gè)高質(zhì)量的服務(wù)就意味著更多的資源得以提供來(lái)更快的完成操作蔫缸。涉及到CPU調(diào)度的優(yōu)先級(jí)、IO優(yōu)先級(jí)际起、任務(wù)運(yùn)行所在的線程以及運(yùn)行的順序等等
通過(guò)設(shè)置屬性qualityOfService來(lái)設(shè)置服務(wù)質(zhì)量拾碌。QoS 有五種優(yōu)先級(jí),默認(rèn)為NSQualityOfServiceDefault街望。它的出現(xiàn)統(tǒng)一了Cocoa中所有多線程技術(shù)的優(yōu)先級(jí)校翔。在此之前,NSOperation和NSThread都通過(guò)threadPriority來(lái)指定優(yōu)先級(jí)灾前,而 GCD 則是根據(jù) DISPATCH_QUEUE_PRIORITY_DEFAULT 等宏定義的整形數(shù)來(lái)指定優(yōu)先級(jí)防症。正確的使用新的 QoS 來(lái)指定線程或任務(wù)優(yōu)先級(jí)可以讓 iOS 更加智能的分配硬件資源,以便于提高執(zhí)行效率和控制電量哎甲。
三蔫敲、相關(guān)類介紹
NSOperation
NSOperation是NSObject的子類,表示單個(gè)工作單元炭玫。它是一個(gè)與任務(wù)的相關(guān)抽象類奈嘿,為狀態(tài)、優(yōu)先級(jí)础嫡、依賴關(guān)系和管理提供了一個(gè)有用的指么、線程安全的結(jié)構(gòu)酝惧。
創(chuàng)建自定義NSOperation子類是沒(méi)有意義的榴鼎,F(xiàn)oundation提供了具體的實(shí)現(xiàn)的子類:NSBlockOperation和NSInvocationOperation。
適合于NSOperation的任務(wù)的例子包括網(wǎng)絡(luò)請(qǐng)求晚唇、圖像調(diào)整巫财、文本處理或任何其他可重復(fù)的、結(jié)構(gòu)化的哩陕、長(zhǎng)時(shí)間運(yùn)行的任務(wù)平项,這些任務(wù)會(huì)產(chǎn)生關(guān)聯(lián)的狀態(tài)或數(shù)據(jù)。
概觀
因?yàn)镹SOperation類是一個(gè)抽象類悍及,并不具備封裝操作的能力闽瓢,所以不能直接使用該類,而是應(yīng)該使用其子類來(lái)執(zhí)行實(shí)際的任務(wù)心赶。其子類包括2種扣讼,系統(tǒng)定義的子類(NSInvocationOperation或NSBlockOperation)和自定義的子類。雖然NSOperation類是抽象類缨叫,但是該類的基本實(shí)現(xiàn)中包括了安全執(zhí)行任務(wù)的重要邏輯椭符。這個(gè)內(nèi)置邏輯的存在可以讓你專注于任務(wù)的實(shí)際實(shí)現(xiàn),而不是專注于編寫(xiě)能保證它與其他系統(tǒng)對(duì)象的正常工作的粘合代碼荔燎。
一個(gè)操作對(duì)象是一個(gè)單發(fā)對(duì)象,也就是說(shuō),一旦它執(zhí)行了其任務(wù),將不能再執(zhí)行一遍销钝。通常通過(guò)添加他們到一個(gè)操作隊(duì)列(NSOperationQueue類的一個(gè)實(shí)例)中來(lái)執(zhí)行操作有咨。操作隊(duì)列通過(guò)讓操作在輔助線程(非主線程)上運(yùn)行,或間接使用libdispatch庫(kù)(也稱為GCD)直接來(lái)執(zhí)行其操作。
如果不想使用一個(gè)操作隊(duì)列,可以調(diào)用start方法直接來(lái)執(zhí)行一個(gè)操作蒸健。手動(dòng)執(zhí)行操作會(huì)增加更多的代碼負(fù)擔(dān),因?yàn)殚_(kāi)啟不在就緒狀態(tài)的操作會(huì)引發(fā)異常座享。ready屬性表示操作的就緒狀態(tài)。
操作依賴
依賴是一種按照特定順序執(zhí)行操作的便捷方式纵装≌鹘玻可以使用addDependency:
、removeDependency:
方法給操作添加和刪除依賴橡娄。默認(rèn)情況下,直到具有依賴的操作對(duì)象的所有依賴都執(zhí)行完成才會(huì)認(rèn)為操作對(duì)象是ready(就緒)狀態(tài)笋婿,酷宵。一旦最后一個(gè)依賴操作完成,這個(gè)操作對(duì)象會(huì)變成就緒狀態(tài)并且可以執(zhí)行。
NSOperation支持的依賴是不會(huì)區(qū)分其操作是成功的完成還是失敗的完成。(換句話說(shuō),取消操作也視為完成君仆。)由你來(lái)決定有依賴的操作在其所依賴的操作被取消或沒(méi)有成功完成任務(wù)的情況下是否應(yīng)該繼續(xù)。這可能需要合并一些額外的錯(cuò)誤跟蹤功能到操作對(duì)象里尊剔。
兼容KVO的屬性
NSOperation類對(duì)其一些屬性是鍵值編碼(KVC)和鍵值觀察(KVO)兼容的崖媚。如有需要,可以觀察這些屬性來(lái)控制應(yīng)用程序的其他部分。使用以下鍵路徑來(lái)觀察屬性:
- isCancelled - 只讀
- isAsynchronous - 只讀
- isExecuting - 只讀
- isFinished - 只讀
- isReady - 只讀
- dependencies - 只讀
- queuePriority - 讀寫(xiě)
- completionBlock - 讀寫(xiě)
雖然可以為這些屬性添加觀察者,但是不應(yīng)該使用Cocoa bindings
來(lái)把它們和用戶界面相關(guān)的元素綁定塑顺。用戶界面相關(guān)的代碼通常只有在應(yīng)用程序的主線程中執(zhí)行汤求。因?yàn)橐粋€(gè)操作可以在任何線程上執(zhí)行,該操作KVO通知同樣可能發(fā)生在任何線程。
如果你為之前的屬性提供了自定義的實(shí)現(xiàn),那么該實(shí)現(xiàn)內(nèi)容必須保持與KVC和KVO的兼容严拒。如果你為NSOperation對(duì)象定義了額外的屬性,建議你同樣需要讓這些屬性保持KVC和KVO兼容扬绪。
多核注意事項(xiàng)
可以從多線程中安全地調(diào)用NSOperation對(duì)象的方法而不需要?jiǎng)?chuàng)建額外的鎖來(lái)同步存取對(duì)象。這種行為是必要的,因?yàn)橐粋€(gè)操作的創(chuàng)建和監(jiān)控通常在一個(gè)單獨(dú)的線程上裤唠。
當(dāng)子類化NSOperation類時(shí),必須確保任何重寫(xiě)的方法能在多個(gè)線程中是安全的調(diào)用挤牛。如果實(shí)現(xiàn)子類中自定義方法,比如自定義數(shù)據(jù)訪問(wèn)器(accessors,getter),必須確保這些方法是線程安全的种蘸。因此,訪問(wèn)任何數(shù)據(jù)變量的操作必須同步,以防止?jié)撛诘臄?shù)據(jù)損壞墓赴。更多關(guān)于信息同步的,可以查看Threading Programming Guide航瞭。
異步操作 VS 同步操作
如果想要手動(dòng)執(zhí)行操作對(duì)象而不是將其添加到一個(gè)隊(duì)列中,那么可以設(shè)計(jì)同步或異步的二種方式來(lái)執(zhí)行操作诫硕。操作對(duì)象默認(rèn)是同步的。在同步操作中,操作對(duì)象不會(huì)創(chuàng)建一個(gè)單獨(dú)的線程來(lái)運(yùn)行它的任務(wù)刊侯。當(dāng)直接調(diào)用同步操作的start方法時(shí),該操作會(huì)在當(dāng)前線程中立即執(zhí)行章办。等到這個(gè)對(duì)象的開(kāi)始(start)方法返回給調(diào)用者時(shí),表示該任務(wù)完成。
當(dāng)你調(diào)用一個(gè)異步操作的start方法時(shí),該方法可能在相應(yīng)的任務(wù)完成前返回纲菌。一個(gè)異步操作對(duì)象負(fù)責(zé)在一個(gè)單獨(dú)線程上調(diào)度任務(wù)挠日。通過(guò)直接開(kāi)啟新一個(gè)線程、調(diào)用一個(gè)異步方法,或者提交一個(gè)block到調(diào)度隊(duì)列來(lái)執(zhí)行這個(gè)操作翰舌。一個(gè)異步操作對(duì)象可以直接啟動(dòng)一個(gè)新線程嚣潜。(具有開(kāi)辟新線程的能力,但是不一定就好開(kāi)啟新線程椅贱,因?yàn)镃PU資源有限懂算,不可能開(kāi)啟無(wú)限個(gè)線程)
如果使用隊(duì)列來(lái)執(zhí)行操作,將他們定義為同步操作是非常簡(jiǎn)單的。如果手動(dòng)執(zhí)行操作,可以將操作對(duì)象定義為異步的庇麦。定義一個(gè)異步操作需要更多的工作,因?yàn)槟惚仨毐O(jiān)控正在進(jìn)行的任務(wù)的狀態(tài)和使用報(bào)告KVO通知狀態(tài)的變化计技。但在你想確保手動(dòng)執(zhí)行操作不會(huì)阻塞調(diào)用線程的情況下定義異步操作是特別有用的。
當(dāng)添加一個(gè)操作到一個(gè)操作隊(duì)列中,隊(duì)列中操作會(huì)忽略了asynchronous屬性的值,總是從一個(gè)單獨(dú)的線程調(diào)用start方法山橄。因此,如果你總是通過(guò)把操作添加到操作隊(duì)列來(lái)運(yùn)行操作,沒(méi)有理由讓他們異步的垮媒。
子類化注釋
NSOperation類提供了基本的邏輯來(lái)跟蹤操作的執(zhí)行狀態(tài),但必須從它派生出子類做實(shí)際工作。如何創(chuàng)建子類依賴于該子類設(shè)計(jì)用于并發(fā)還是非并發(fā)航棱。
方法的重載
對(duì)于非并發(fā)操作,通常只覆蓋一個(gè)方法
- main
在該方法中,需要給執(zhí)行特定的任務(wù)添加必要的代碼睡雇。當(dāng)然,也可以定義一個(gè)自定義的初始化方法,讓它更容易創(chuàng)建自定義類的實(shí)例。你可能還想定義getter和setter方法來(lái)從操作訪問(wèn)數(shù)據(jù)饮醇。然而,如果你定義定制了getter和setter方法,你必須確保這些方法在多個(gè)線程調(diào)用是安全的它抱。
如果你創(chuàng)建一個(gè)并發(fā)操作,需要至少重寫(xiě)下面的方法和屬性:
- start
- asynchronous
- executing
- finished
在并發(fā)操作中,start方法負(fù)責(zé)以異步的方式啟動(dòng)操作。從這個(gè)方法決定否生成一個(gè)線程或者調(diào)用異步函數(shù)朴艰。在將要開(kāi)始操作時(shí),start方法也應(yīng)該更新操作executing屬性的執(zhí)行狀態(tài)作為報(bào)告观蓄。這可以通過(guò)發(fā)送executing這個(gè)鍵路徑的KVO通知,讓感興趣的客戶端知道該操作現(xiàn)在正在運(yùn)行中。executing屬性還必須以線程安全的方式提供狀態(tài)祠墅。
在將要完成或取消任務(wù)時(shí),并發(fā)操作對(duì)象必須生成isExecuting和isFinished鍵路徑的KVO通知為來(lái)標(biāo)記操作的最終改變狀態(tài)侮穿。(在取消的情況下,更新isFinished鍵路徑仍然是重要的,即使操作沒(méi)有完全完成其任務(wù)。已經(jīng)排隊(duì)的操作必須在隊(duì)列刪除操作前報(bào)告)除了生成KVO通知,executing和finished屬性的重寫(xiě)還應(yīng)該繼續(xù)根據(jù)操作的狀態(tài)的精確值來(lái)報(bào)告饵隙。
重要:
在start方法中撮珠,任何時(shí)候都不應(yīng)該調(diào)用super沮脖。當(dāng)定義一個(gè)并發(fā)操作時(shí)金矛,需要自己提供與默認(rèn)start方法相同的行為,包括啟動(dòng)任務(wù)和生成適當(dāng)?shù)腒VO通知勺届。start方法還應(yīng)該在實(shí)際開(kāi)始任務(wù)之前檢查操作本身是否被取消驶俊。
對(duì)于并發(fā)操作,除了上面描述的方法之外免姿,應(yīng)該不需要重寫(xiě)其他方法饼酿。然而,如果你自定義操作的依賴特性,可能必須重寫(xiě)額外的方法并提供額外的KVO通知故俐。對(duì)于依賴項(xiàng)想鹰,這可能只需要提供isReady鍵路徑的通知。因?yàn)閐ependencies屬性包含了一系列依賴操作药版,所以對(duì)它的更改已經(jīng)由默認(rèn)的NSOperation類處理辑舷。
維護(hù)操作對(duì)象狀態(tài)
操作對(duì)象通過(guò)維護(hù)內(nèi)容的狀態(tài)信息來(lái)決定何時(shí)執(zhí)行是安全的和在操作的生命周期期間通知外部其任務(wù)進(jìn)展。自定義子類需要維護(hù)狀態(tài)信息來(lái)保證代碼中執(zhí)行操作的正確性槽片。操作狀態(tài)關(guān)聯(lián)的鍵路徑有:
-
isReady
該鍵路徑讓客戶端知道一個(gè)操作何時(shí)可以準(zhǔn)備執(zhí)行何缓。當(dāng)操作馬上可以執(zhí)行時(shí)該屬性值為true,當(dāng)其依賴中有未完成还栓,則是false碌廓。
大多數(shù)情況下,沒(méi)必要自己管理這個(gè)鍵路徑的狀態(tài)剩盒。如果操作的就緒狀態(tài)是由操作依賴因素決定(例如在你的程序中的一些外部條件)谷婆,那么你可以提供ready屬性的實(shí)現(xiàn)并且跟蹤操作的就緒狀態(tài)。雖然只在外部狀態(tài)允許的情況下創(chuàng)建操作對(duì)象時(shí)通常更簡(jiǎn)單辽聊。在macOS 10.6或更高版本中波材,如果取消的操作,正在等待一個(gè)或多個(gè)依賴操作完成身隐,那么這些依賴項(xiàng)將被忽略廷区,該屬性的值將更新成已經(jīng)準(zhǔn)備好運(yùn)行了。這種行為使操作隊(duì)列有機(jī)會(huì)更快地將已取消的操作從隊(duì)列中清除出去贾铝。
-
isExecuting
該鍵路徑讓客戶端知道操作是否在正在地執(zhí)行它所分配的任務(wù)隙轻。如果操作正在處理其任務(wù),則值為true;否則值為false垢揩。
如果替換操作對(duì)象的start方法玖绿,則還必須替換executing屬性,并在操作的執(zhí)行狀態(tài)發(fā)生變化時(shí)生成KVO通知叁巨。
-
isFinished
該鍵路徑讓客戶端知道操作成功地完成了任務(wù)或者被取消并退出斑匪。直到isFinished這個(gè)鍵路徑的值變?yōu)閠rue,操作對(duì)象才會(huì)清除依賴锋勺。類似的蚀瘸,直到finished屬性的是true時(shí),一個(gè)操作隊(duì)列才會(huì)退出操作隊(duì)列庶橱。因此贮勃,將操作標(biāo)記為已完成對(duì)于防止隊(duì)列備份正在進(jìn)行的操作或已取消的操作非常重要。
如果替換操作對(duì)象的start方法苏章,則還必須替換executing屬性寂嘉,并在操作的執(zhí)行狀態(tài)發(fā)生變化時(shí)生成KVO通知奏瞬。
-
isCancelled
isCancelled鍵路徑讓客戶端知道請(qǐng)求取消某個(gè)操作。支持自愿取消泉孩,但不鼓勵(lì)主動(dòng)發(fā)送這個(gè)鍵路徑的KVO通知硼端。
響應(yīng)取消命令
一旦將操作添加到隊(duì)列中,操作就不在你的控制范圍內(nèi)了寓搬。隊(duì)列接管并處理該任務(wù)的調(diào)度显蝌。但是,如果你最終決定不想執(zhí)行某些操作订咸,例如用戶按下取消按鈕或退出應(yīng)用程序時(shí)曼尊,你可以取消操作,以防止消耗不必要地CPU時(shí)間脏嚷÷嫫玻可以通過(guò)調(diào)用操作對(duì)象本身的cancel方法或調(diào)用NSOperationQueue類的cancelAllOperations方法來(lái)實(shí)現(xiàn)這一點(diǎn)。
取消一個(gè)操作不會(huì)立即迫使它停止它正在做的事情父叙。雖然所有操作都需要考慮cancelled屬性中的值神郊,但是必須顯式檢查該屬性中的值,并根據(jù)需要中止趾唱。NSOperation的默認(rèn)實(shí)現(xiàn)包括取消檢查涌乳。例如,如果在調(diào)用一個(gè)操作的start方法之前取消該操作甜癞,那么start方法將退出而不啟動(dòng)任務(wù)夕晓。
提示
在macOS 10.6或更高版本中,如果調(diào)用操作隊(duì)列中的操作的cancel方法悠咱,且該操作隊(duì)列具有未完成的依賴操作蒸辆,那么這些依賴操作隨后將被忽略。由于操作已經(jīng)被取消析既,因此此行為允許隊(duì)列調(diào)用操作的start方法躬贡,以便在不調(diào)用其主方法的情況下從隊(duì)列中刪除操作。如果對(duì)不在隊(duì)列中的操作調(diào)用cancel方法眼坏,則該操作立即標(biāo)記為已取消拂玻。在每種情況下,將操作標(biāo)記為已準(zhǔn)備好或已完成時(shí)宰译,會(huì)生成適當(dāng)?shù)腒VO通知檐蚜。
在你編寫(xiě)的任何定制代碼中,都應(yīng)該始終支持取消語(yǔ)義囤屹。特別是熬甚,主任務(wù)代碼應(yīng)該定期檢查cancelled屬性的值逢渔。如果屬性值為YES肋坚,則操作對(duì)象應(yīng)該盡快清理并退出。如果您實(shí)現(xiàn)了一個(gè)自定義的start方法,那么該方法應(yīng)該包含早期的取消檢查并適當(dāng)?shù)貓?zhí)行智厌。您的自定義開(kāi)始方法必須準(zhǔn)備好處理這種類型的提前取消诲泌。
除了在操作被取消時(shí)簡(jiǎn)單地退出之外,將已取消的操作移動(dòng)到適當(dāng)?shù)淖罱K狀態(tài)也很重要铣鹏。具體來(lái)說(shuō)敷扫,如果您自己管理finished和executing屬性的值(可能是因?yàn)槟阏趯?shí)現(xiàn)并發(fā)操作),那么你必須更新更新相應(yīng)地屬性诚卸。具體來(lái)說(shuō)葵第,你必須將finished返回的值更改為YES,將executing返回的值更改為NO合溺。即使操作在開(kāi)始執(zhí)行之前被取消卒密,你也必須進(jìn)行這些更改。
屬性和方法
初始化
// 返回一個(gè)初始化的NSOperation對(duì)象
- (instancetype)init;// 父類 NSObject方法
執(zhí)行操作
// 開(kāi)啟操作
//在當(dāng)前任務(wù)狀態(tài)和依賴關(guān)系合適的情況下棠赛,啟動(dòng)NSOperation的main方法任務(wù)哮奇,需要注意缺省實(shí)現(xiàn)只是在當(dāng)前線程運(yùn)行。如果需要并發(fā)執(zhí)行睛约,子類必須重寫(xiě)這個(gè)方法鼎俘,并且使屬性asynchronous返回YES。
- (void)start;
// 執(zhí)行接收者(NSOperation)的非并發(fā)任務(wù)辩涝。操作任務(wù)的入口贸伐,一般用于自定義NSOperation的子類
- (void)main;
// 操作主任務(wù)完成后執(zhí)行這個(gè)block
// 由于NSOperation有可能被取消怔揩,所以在block運(yùn)行的代碼應(yīng)該和NSOperation的核心任務(wù)無(wú)關(guān)
@property (nullable, copy) void (^completionBlock)(void);
取消操作
// 通知操作對(duì)象(NSOperation)停止執(zhí)行其任務(wù)。標(biāo)記isCancelled狀態(tài)。
// 調(diào)用后不會(huì)自動(dòng)馬上取消茬缩,需要通過(guò)isCancelled方法檢查是否被取消圈暗,然后自己編寫(xiě)代碼退出當(dāng)前的操作
- (void)cancel;
獲取操作狀態(tài)
// Boolean 值,表示操作是否已經(jīng)取消
@property (readonly, getter=isCancelled) BOOL cancelled;
// Boolean 值,表示操作是否正在執(zhí)行
@property (readonly, getter=isExecuting) BOOL executing;
// Boolean 值渺鹦,表示操作是否正完成執(zhí)行
@property (readonly, getter=isFinished) BOOL finished;
// Boolean 值,表示操作是否異步執(zhí)行任務(wù)
@property (readonly, getter=isAsynchronous) BOOL asynchronous ;
// Boolean 值,表示操作是否可以立即執(zhí)行(準(zhǔn)備完畢狀態(tài))
@property (readonly, getter=isReady) BOOL ready;
// 操作的名字
@property (nullable, copy) NSString *name;
管理依賴
// 添加依賴竖幔,使接收器依賴于指定完成操作馋评。
// 如:[op1 addDependency:op2]; op2先執(zhí)行纠脾,op1后執(zhí)行
- (void)addDependency:(NSOperation *)op;
// 取消依賴右核,移出接收方對(duì)指定操作的依賴
// 注意:操作對(duì)象的依賴不能在操作隊(duì)列執(zhí)行時(shí)取消
- (void)removeDependency:(NSOperation *)op;
// 在當(dāng)前對(duì)象開(kāi)始執(zhí)行之前必須完成執(zhí)行的操作對(duì)象數(shù)組菱鸥。
@property (readonly, copy) NSArray<NSOperation *> *dependencies;
執(zhí)行優(yōu)先級(jí)
// 操作獲取系統(tǒng)資源的相對(duì)的重要性宗兼。系統(tǒng)自動(dòng)合理的管理隊(duì)列的資源分配
@property NSQualityOfService qualityOfService;
等待一個(gè)操作對(duì)象
// 阻塞當(dāng)前線程的執(zhí)行针炉,直到操作對(duì)象完成其任務(wù)挠他。可用于線程執(zhí)行順序的同步。
- (void)waitUntilFinished;
常量
// 這些常量允許您對(duì)執(zhí)行操作的順序進(jìn)行優(yōu)先排序茉唉。
NSOperationQueuePriority
// 用于表示工作對(duì)系統(tǒng)的性質(zhì)和重要性艾凯。服務(wù)質(zhì)量較高的類比服務(wù)質(zhì)量較低的類獲得更多的資源。
NSQualityOfService
// NSOperation優(yōu)先級(jí)的枚舉
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
在iOS8之后蘋果提供了幾個(gè)Quality of Service枚舉來(lái)使用:user interactive, user initiated, utility 和 background懂傀。通過(guò)這些枚舉告訴系統(tǒng)我們?cè)谶M(jìn)行什么樣的工作趾诗,然后系統(tǒng)會(huì)通過(guò)合理的資源控制來(lái)最高效的執(zhí)行任務(wù)代碼,其中主要涉及到CPU調(diào)度的優(yōu)先級(jí)蹬蚁、IO優(yōu)先級(jí)恃泪、任務(wù)運(yùn)行在哪個(gè)線程以及運(yùn)行的順序等等,我們可以通過(guò)一個(gè)抽象的Quality of Service枚舉參數(shù)來(lái)表明任務(wù)的意圖以及類別
//與用戶交互的任務(wù)犀斋,這些任務(wù)通常跟UI級(jí)別的刷新相關(guān)贝乎,比如動(dòng)畫(huà),這些任務(wù)需要在一瞬間完成.
NSQualityOfServiceUserInteractive
// 由用戶發(fā)起的并且需要立即得到結(jié)果的任務(wù)叽粹,比如滑動(dòng)scroll view時(shí)去加載數(shù)據(jù)用于后續(xù)cell的顯示糕非,這些任務(wù)通常跟后續(xù)的用戶交互相關(guān),在幾秒或者更短的時(shí)間內(nèi)完成
NSQualityOfServiceUserInitiated
// 一些可能需要花點(diǎn)時(shí)間的任務(wù)球榆,這些任務(wù)不需要馬上返回結(jié)果朽肥,比如下載的任務(wù),這些任務(wù)可能花費(fèi)幾秒或者幾分鐘的時(shí)間
NSQualityOfServiceUtility
// 一些可能需要花點(diǎn)時(shí)間的任務(wù)持钉,這些任務(wù)不需要馬上返回結(jié)果衡招,比如下載的任務(wù),這些任務(wù)可能花費(fèi)幾秒或者幾分鐘的時(shí)間
NSQualityOfServiceBackground
// 一些可能需要花點(diǎn)時(shí)間的任務(wù)每强,這些任務(wù)不需要馬上返回結(jié)果始腾,比如下載的任務(wù)州刽,這些任務(wù)可能花費(fèi)幾秒或者幾分鐘的時(shí)間
NSQualityOfServiceDefault
eg:Utility 及以下的優(yōu)先級(jí)會(huì)受到 iOS9 中低電量模式的控制。另外浪箭,在沒(méi)有用戶操作時(shí)穗椅,90% 任務(wù)的優(yōu)先級(jí)都應(yīng)該在 Utility 之下。
NSBlockOperation
NSOperation的子類奶栖,管理一個(gè)或多個(gè)塊的并發(fā)執(zhí)行的操作匹表。
概觀
NSBlockOperation類是NSOperation的一個(gè)具體子類,它管理一個(gè)或多個(gè)塊的并發(fā)執(zhí)行宣鄙∨鄱疲可以使用此對(duì)象一次執(zhí)行多個(gè)塊,而不必為每個(gè)塊創(chuàng)建單獨(dú)的操作對(duì)象冻晤。當(dāng)執(zhí)行多個(gè)塊時(shí)苇羡,只有當(dāng)所有塊都完成執(zhí)行時(shí),才認(rèn)為操作本身已經(jīng)完成鼻弧。
添加到操作中的塊(block)將以默認(rèn)優(yōu)先級(jí)分配到適當(dāng)?shù)墓ぷ麝?duì)列设江。
方法屬性
管理操作中的塊
// 創(chuàng)建并返回一個(gè)NSBlockOperation對(duì)象,并添加指定的塊到該對(duì)象中攘轩。
+ (instancetype)blockOperationWithBlock:(void (^)(void))block;
// 將指定的塊添加到要執(zhí)行的塊列表中叉存。
- (void)addExecutionBlock:(void (^)(void))block;
// 與接收器關(guān)聯(lián)的塊。
@property (readonly, copy) NSArray<void (^)(void)> *executionBlocks;
NSInvocationOperation
NSOperation的子類,管理作為調(diào)用指定的單個(gè)封裝任務(wù)執(zhí)行的操作撑刺。
概觀
NSInvocationOperation類是NSOperation的一個(gè)具體子類鹉胖,可以使用它來(lái)初始化一個(gè)包含在指定對(duì)象上調(diào)用選擇器的操作。這個(gè)類實(shí)現(xiàn)了一個(gè)非并發(fā)操作够傍。
方法屬性
初始化
// 返回一個(gè)用指定的目標(biāo)和選擇器初始化的NSInvocationOperation對(duì)象甫菠。
- (instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;
// 返回用指定的調(diào)用對(duì)象初始化的NSInvocationOperation對(duì)象。
- (instancetype)initWithInvocation:(NSInvocation *)inv NS_DESIGNATED_INITIALIZER;
獲取屬性
// 接收者的調(diào)用對(duì)象冕屯。
@property (readonly, retain) NSInvocation *invocation;
// 調(diào)用或方法的結(jié)果
@property (nullable, readonly, retain) id result;
常量
// 如果調(diào)用result方法時(shí)出現(xiàn)錯(cuò)誤寂诱,則由NSInvocationOperation引發(fā)的異常名稱。
Result Exceptions
NSOperationQueue
管理操作執(zhí)行的隊(duì)列安聘。
概觀
NSObject子類痰洒。操作隊(duì)列根據(jù)其優(yōu)先級(jí)和就緒程度執(zhí)行其排隊(duì)的NSOperation對(duì)象。在添加到操作隊(duì)列后浴韭,操作將保持在其隊(duì)列中丘喻,直到它報(bào)告其任務(wù)結(jié)束為止。在隊(duì)列被添加后念颈,您不能直接從隊(duì)列中刪除操作泉粉。
提示:
操作隊(duì)列保留操作直到完成,隊(duì)列本身保留到所有操作完成。使用未完成的操作掛起操作隊(duì)列可能導(dǎo)致內(nèi)存泄漏嗡靡。
確定執(zhí)行順序
隊(duì)列中的操作是根據(jù)它們的狀態(tài)跺撼、優(yōu)先級(jí)和依賴關(guān)系來(lái)組織的,并相應(yīng)地執(zhí)行讨彼。如果所有排隊(duì)的操作都具有相同的queuePriority并準(zhǔn)備好在放入隊(duì)列時(shí)執(zhí)行(也就是說(shuō)歉井,它們的就緒屬性返回yes),那么它們將按照提交到隊(duì)列的順序執(zhí)行哈误。否則哩至,操作隊(duì)列總是執(zhí)行優(yōu)先級(jí)最高的操作。
但是不應(yīng)該依賴隊(duì)列語(yǔ)義來(lái)確保操作的特定執(zhí)行順序黑滴,因?yàn)椴僮鳒?zhǔn)備狀態(tài)的更改可能會(huì)更改最終的執(zhí)行順序憨募。操作間依賴關(guān)系為操作提供了絕對(duì)的執(zhí)行順序紧索,即使這些操作位于不同的操作隊(duì)列中袁辈。一個(gè)操作對(duì)象直到它的所有依賴操作都完成執(zhí)行后才被認(rèn)為準(zhǔn)備好執(zhí)行。
取消操作
結(jié)束任務(wù)并不一定意味著操作完成了任務(wù)珠漂,一個(gè)操作也可以被取消晚缩。取消操作對(duì)象會(huì)將該對(duì)象留在隊(duì)列中,但會(huì)通知該對(duì)象應(yīng)該盡快停止其任務(wù)媳危。對(duì)于當(dāng)前正在執(zhí)行的操作荞彼,這意味著操作對(duì)象必須檢查取消狀態(tài),停止它正在執(zhí)行的操作待笑,并將自己標(biāo)記為已結(jié)束鸣皂。對(duì)于在隊(duì)列排隊(duì)但尚未執(zhí)行的操作,隊(duì)列仍然需要調(diào)用操作對(duì)象的start方法暮蹂,以便它能夠處理取消事件并將自己標(biāo)記為已結(jié)束寞缝。
提示
取消操作會(huì)導(dǎo)致操作忽略它可能具有的依賴項(xiàng)。這種行為使隊(duì)列能夠盡快執(zhí)行操作的start方法仰泻。開(kāi)始方法依次將操作移動(dòng)到結(jié)束狀態(tài)荆陆,以便可以將其從隊(duì)列中刪除。
KVO兼容屬性
NSOperationQueue類是符合鍵值編碼(KVC)和鍵值觀察(KVO)的集侯”惶洌可以根據(jù)需要觀察這些屬性,以控制應(yīng)用程序的其他部分棠枉。要觀察屬性浓体,使用以下鍵路徑:
- operations - 只讀
- operationCount - 只讀
- maxConcurrentOperationCount - 讀寫(xiě)
- suspended - 讀寫(xiě)
- name - 讀寫(xiě)
雖然可以將觀察者附加到這些屬性,但是不應(yīng)該使用Cocoa bindings(綁定)將它們綁定到用戶界面的相關(guān)的元素辈讶。與用戶界面關(guān)聯(lián)的任務(wù)通常只能在應(yīng)用程序的主線程中執(zhí)行命浴。然而與操作隊(duì)列相關(guān)聯(lián)的KVO通知可能發(fā)生在任何線程中。
線程安全
從多個(gè)線程中使用一個(gè)NSOperationQueue對(duì)象是安全的荞估,無(wú)需創(chuàng)建額外的鎖來(lái)同步對(duì)該對(duì)象的訪問(wèn)咳促。
操作隊(duì)列使用調(diào)度框架來(lái)啟動(dòng)其操作的執(zhí)行稚新。因此,操作總是在單獨(dú)的線程上執(zhí)行,而不管它們是被指定為同步的還是異步的盹愚。
屬性&方法
訪問(wèn)特定操作隊(duì)列
// 返回與主線程關(guān)聯(lián)的操作隊(duì)列坏挠。缺省總是有一個(gè)queue。
@property (class, readonly, strong) NSOperationQueue *mainQueue;
// 返回啟動(dòng)當(dāng)前操作的操作隊(duì)列屯阀。
@property (class, readonly, strong, nullable) NSOperationQueue *currentQueue;
管理隊(duì)列中的操作
// 將指定的操作添加到接收器。
- (void)addOperation:(NSOperation *)op;
//將指定的操作添加到隊(duì)列轴术。
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait;
// 在操作中包裝指定的塊并將其添加到接收器难衰。
- (void)addOperationWithBlock:(void (^)(void))block;
// 當(dāng)前在隊(duì)列中的操作。
@property (readonly, copy) NSArray<__kindof NSOperation *> *operations;
// 隊(duì)列中當(dāng)前的操作數(shù)逗栽。
@property (readonly) NSUInteger operationCount;
// 取消所有排隊(duì)和執(zhí)行的操作盖袭。
- (void)cancelAllOperations;
// 阻塞當(dāng)前線程,直到所有接收者的排隊(duì)操作和執(zhí)行操作完成為止
- (void)waitUntilAllOperationsAreFinished;
管理操作的執(zhí)行
// 應(yīng)用于使用隊(duì)列執(zhí)行的操作的默認(rèn)服務(wù)級(jí)別彼宠。
@property NSQualityOfService qualityOfService;
// 可以同時(shí)執(zhí)行的隊(duì)列操作的最大數(shù)量鳄虱。
@property NSInteger maxConcurrentOperationCount;
// 在隊(duì)列中并發(fā)執(zhí)行的默認(rèn)最大操作數(shù)。
NSOperationQueueDefaultMaxConcurrentOperationCount
暫停執(zhí)行
// 一個(gè)布爾值凭峡,表示隊(duì)列是否在主動(dòng)調(diào)度要執(zhí)行的操作拙已。(suspended 掛起,暫停的)
@property (getter=isSuspended) BOOL suspended;
當(dāng)該屬性的值為NO時(shí)摧冀,隊(duì)列將積極啟動(dòng)隊(duì)列中已準(zhǔn)備執(zhí)行的操作倍踪。將此屬性設(shè)置為YES時(shí),可以防止隊(duì)列啟動(dòng)任何排隊(duì)著的操作索昂,但是已經(jīng)執(zhí)行的操作將繼續(xù)執(zhí)行建车。可以繼續(xù)將操作添加到已掛起的隊(duì)列中楼镐,但在將此屬性更改為NO之前癞志,這些操作不會(huì)安排執(zhí)行。
操作只有在結(jié)束執(zhí)行后才從隊(duì)列中刪除框产。但是凄杯,為了結(jié)束執(zhí)行,必須首先啟動(dòng)一個(gè)操作秉宿。因?yàn)閽炱鸬年?duì)列不會(huì)啟動(dòng)任何新操作戒突,所以它不會(huì)刪除當(dāng)前排隊(duì)但未執(zhí)行的任何操作(包括已取消的操作)。
可以使用鍵值觀察監(jiān)視此屬性值的更改描睦。配置一個(gè)觀察者來(lái)監(jiān)視操作隊(duì)列的suspended鍵路徑膊存。
此屬性的默認(rèn)值是NO。
隊(duì)列配置
// 操作隊(duì)列名稱
@property (nullable, copy) NSString *name;
// 用于執(zhí)行操作的調(diào)度隊(duì)列。
@property (nullable, assign /* actually retain */) dispatch_queue_t underlyingQueue;
四隔崎、使用
1. NSInvocationOperation
創(chuàng)建:調(diào)用Start方法開(kāi)啟今艺。默認(rèn)情況下,調(diào)用start方法不會(huì)開(kāi)辟一個(gè)新線程去執(zhí)行操作爵卒,而是在當(dāng)前線程同步執(zhí)行操作虚缎。
創(chuàng)建方式一:使用initWithInvocation方法,可以設(shè)置0個(gè)或多個(gè)參數(shù)
NSMethodSignature *sig = [[self class] instanceMethodSignatureForSelector:@selector(addSig:)];
NSInvocation *invo = [NSInvocation invocationWithMethodSignature:sig];
NSString * info = @"NSMethodSignature";
[invo setTarget:self];
[invo setSelector:@selector(addSig:)];
//argumentLocation 指定參數(shù)钓株,以指針?lè)绞? // idx 參數(shù)索引实牡,第一個(gè)參數(shù)的起始index是2,因?yàn)閕ndex為1轴合,2的分別是self和selector
[invo setArgument:(__bridge void *)(info) atIndex:2];
NSInvocationOperation *invocationOp = [[NSInvocationOperation alloc] initWithInvocation:invo];
[invocationOp start];
創(chuàng)建方式二:使用initWithTarget
// 初始化
NSInvocationOperation *invocationOp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOpSel:) object:@"111"]; // 操作的第一個(gè)
// 執(zhí)行
[invocationOp start];
2. NSBlockOperation
創(chuàng)建第一個(gè)操作任務(wù)创坞,一般不會(huì)開(kāi)辟新線程,就在當(dāng)前線程中執(zhí)行受葛。之后的任務(wù)都是開(kāi)辟新線程题涨。執(zhí)行異步任務(wù)。
創(chuàng)建方式一:使用init:創(chuàng)建操作對(duì)象奔坟,然后使用addExecutionBlock:添加執(zhí)行
NSBlockOperation * op1 = [[NSBlockOperation alloc] init];
[op1 addExecutionBlock:^{
NSLog(@"1 beign");
NSLog(@"1--%@",[NSThread currentThread]);
NSLog(@"1 end");
}];
[op addExecutionBlock:^{
NSLog(@"2 beign");
NSLog(@"2--%@,currentQueue >>>> %@",[NSThread currentThread],[NSOperationQueue currentQueue]);
NSLog(@"2 end");
}];
[op addExecutionBlock:^{
NSLog(@"3 beign");
NSLog(@"3--%@,currentQueue >>>> %@",[NSThread currentThread],[NSOperationQueue currentQueue]);
NSLog(@"3 end");
}];
[op1 start];
創(chuàng)建方式二:使用blockOperationWithBlock創(chuàng)建操作對(duì)象
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1 beign");
NSLog(@"1--%@,currentQueue >>>> %@",[NSThread currentThread],[NSOperationQueue currentQueue]); // 第一個(gè)操作任務(wù)携栋,一般不會(huì)開(kāi)辟新線程搭盾。就在當(dāng)前線程中執(zhí)行
NSLog(@"1 end");
}];
// 以下操作任務(wù)咳秉,會(huì)開(kāi)辟新線程
[op addExecutionBlock:^{
NSLog(@"2 beign");
NSLog(@"2--%@,currentQueue >>>> %@",[NSThread currentThread],[NSOperationQueue currentQueue]);
NSLog(@"2 end");
}];
[op addExecutionBlock:^{
NSLog(@"3 beign");
NSLog(@"3--%@,currentQueue >>>> %@",[NSThread currentThread],[NSOperationQueue currentQueue]);
NSLog(@"3 end");
}];
[op start];
3. NSOperationQueue
3.1. 將操作對(duì)象添加到隊(duì)列中
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *blockOp = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1 beign");
NSLog(@"1--%@",[NSThread currentThread]);
NSLog(@"1 end");
}];
[queue addOperation:blockOp];
3.2. 添加依賴
直接使用start啟動(dòng)一個(gè)操作對(duì)象而非將操作對(duì)象添加到NSOperationQueue對(duì)象中是沒(méi)有意義的。因?yàn)楫?dāng)給操作對(duì)象發(fā)送start消息后鸯隅,啟動(dòng)操作澜建,如果線程未阻塞會(huì)立即執(zhí)行該任務(wù)。所以就沒(méi)有所謂的執(zhí)行順序蝌以。只有將操作對(duì)象添加到NSOperationQueue對(duì)象中炕舵,在隊(duì)列調(diào)度的時(shí)候,可以按照依賴跟畅、優(yōu)先級(jí)等因素順序的調(diào)度任務(wù)咽筋。
注意:一定要在添加線程對(duì)象NSOperationQueue之前,進(jìn)行依賴設(shè)置徊件。否則依賴將無(wú)法達(dá)到預(yù)期效果奸攻。
a. 通隊(duì)列之間的依賴
// 創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 創(chuàng)建操作
NSInvocationOperation *invocationOp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOpSel:) object:@"invocationOp--arg"];
NSInvocationOperation *invocationOp2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOp2Sel:) object:@"invocationOp2--arg"];
// 設(shè)置依賴,操作invocationOp2的任務(wù)執(zhí)行完虱痕,才會(huì)執(zhí)行操作invocationOp的任務(wù)睹耐。
[invocationOp addDependency:invocationOp2];
// 執(zhí)行
[queue addOperation:invocationOp];
[queue addOperation:invocationOp2];
b. 不同隊(duì)列間的依賴
// 創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 創(chuàng)建操作
NSBlockOperation *block1Op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block1Op -- begin");
[NSThread sleepForTimeInterval:3]; // 模擬耗時(shí)操作
NSLog(@"block1Op -- end");
}];
NSBlockOperation *block2Op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block2Op -- begin");
[NSThread sleepForTimeInterval:4]; // 模擬耗時(shí)操作
NSLog(@"block2Op -- end");
}];
// 創(chuàng)建隊(duì)列
NSOperationQueue *queue2 = [[NSOperationQueue alloc] init];
// 創(chuàng)建操作
NSBlockOperation *block3Op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block3Op -- begin");
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"block3Op -- end");
}];
NSBlockOperation *block4Op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block4Op -- begin");
[NSThread sleepForTimeInterval:1]; // 模擬耗時(shí)操作
NSLog(@"block4Op -- end");
}];
// 設(shè)置依賴,操作invocationOp2的任務(wù)執(zhí)行完部翘,才會(huì)執(zhí)行操作invocationOp的任務(wù)硝训。
[block1Op addDependency:block3Op];
[block3Op addDependency:block2Op];
// block2Op --> block3Op --> block1Op
// 添加操作到隊(duì)列中
[queue addOperation:block1Op];
[queue addOperation:block2Op];
[queue2 addOperation:block3Op];
[queue2 addOperation:block4Op];
從上代碼可以得到block1Op、block2Op、block3Op三個(gè)操作的執(zhí)行順序:block2Op --> block3Op --> block1Op窖梁。
// 創(chuàng)建操作
NSBlockOperation *blockOp = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"blockOp");
// 模擬耗時(shí)操作
[NSThread sleepForTimeInterval:3];
}];
NSBlockOperation *block2Op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block2Op -- begin");
// 等blockOp操作對(duì)象的任務(wù)執(zhí)行完赘风,才能接著往下執(zhí)行
[blockOp waitUntilFinished];
NSLog(@"block2Op --end");
}];
// 執(zhí)行
[queue addOperation:blockOp];
[queue addOperation:block2Op];
3.3. 獲取屬性獲取主隊(duì)列
NSOperationQueue *queue = [NSOperationQueue mainQueue];
3.4. 獲取屬性獲取當(dāng)前隊(duì)列
NSOperationQueue *queue = [NSOperationQueue currentQueue];
3.5. 進(jìn)度修改:NSOperationQueue隊(duì)列的暫停、繼續(xù)和取消纵刘。
// 初始化隊(duì)列
- (NSOperationQueue *)manualQueue{
if (!_manualQueue) {
_manualQueue = [NSOperationQueue new];
_manualQueue.maxConcurrentOperationCount = 2;
}
return _manualQueue;
}
NSBlockOperation *blockOperation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1--start");
[NSThread sleepForTimeInterval:3];
NSLog(@"1--end");
}];
NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2--start");
[NSThread sleepForTimeInterval:1];
NSLog(@"2--end");
}];
NSBlockOperation *blockOperation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"3--start");
[NSThread sleepForTimeInterval:4];
NSLog(@"3--end");
}];
NSBlockOperation *blockOperation4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"4--start");
[NSThread sleepForTimeInterval:3];
NSLog(@"4--end");
}];
[self.manualQueue addOperation:blockOperation1];
[self.manualQueue addOperation:blockOperation2];
[self.manualQueue addOperation:blockOperation3];
[self.manualQueue addOperation:blockOperation4];
a. 暫停
如果任務(wù)正在執(zhí)行將不會(huì)受到影響贝次。因?yàn)槿蝿?wù)已經(jīng)被隊(duì)列調(diào)度到一個(gè)線程上并執(zhí)行。當(dāng)NSOperationQueue對(duì)象屬性suspended設(shè)置為YES彰导,是隊(duì)列停止了對(duì)任務(wù)調(diào)度蛔翅。對(duì)那些還在線程中的操作有影響的。
self.manualQueue.suspended = YES;
b. 繼續(xù)
隊(duì)列將積極啟動(dòng)隊(duì)列中已準(zhǔn)備執(zhí)行的操作位谋。
self.manualQueue.suspended = NO;
c. 取消
對(duì)于隊(duì)列中的操作山析,只有操作標(biāo)記為已結(jié)束才能被隊(duì)列移除。
- 在隊(duì)列中未被調(diào)度的操作掏父,會(huì)調(diào)用start方法執(zhí)行操作笋轨,以便操作對(duì)象處理取消事件。然后標(biāo)記這些操作對(duì)象為已結(jié)束赊淑。
- 對(duì)于正在線程中執(zhí)行其任務(wù)的操作對(duì)象爵政,正在執(zhí)行的任務(wù)會(huì)繼續(xù)執(zhí)行,該操作對(duì)象會(huì)被標(biāo)記經(jīng)結(jié)束陶缺。
[self.manualQueue cancelAllOperations];
3.6. 操作完成
a. 監(jiān)聽(tīng)操作完成
可以在操作執(zhí)行完成后钾挟,添加額外的內(nèi)容。使用屬性completionBlock饱岸,可以為NSOperation對(duì)象的任務(wù)完成后添加額外的操作掺出。但是不可在completionBlock中追加任務(wù),因?yàn)椴僮?operation)已經(jīng)啟動(dòng)執(zhí)行或者結(jié)束后不可以添加block任務(wù)苫费。
NSBlockOperation *blockOperation1 = [NSBlockOperation blockOperationWithBlock:^{
// 添加的任務(wù)
}];
blockOperation1.completionBlock = ^{
// 添加額外的內(nèi)容
};
[blockOperation1 start];
b. 監(jiān)聽(tīng)操作完成
當(dāng)執(zhí)行到某個(gè)操作對(duì)象發(fā)送了一個(gè)消息waitUntilFinished:
消息汤锨。當(dāng)前線程會(huì)被阻塞,之前發(fā)送消息的操作對(duì)象的任務(wù)執(zhí)行完畢百框。當(dāng)前線程才會(huì)被喚起闲礼,進(jìn)入準(zhǔn)備狀態(tài),開(kāi)始執(zhí)行相應(yīng)的任務(wù)铐维。
// 創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 創(chuàng)建操作
NSBlockOperation *blockOp = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:3]; // 模擬耗時(shí)操作
}];
NSBlockOperation *block2Op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block2Op -- begin");
[blockOp waitUntilFinished]; // 等blockOp操作對(duì)象的任務(wù)執(zhí)行完柬泽,才能接著往下執(zhí)行
NSLog(@"block2Op --end");
}];
// 執(zhí)行
[queue addOperation:blockOp];
[queue addOperation:block2Op];
3.7. 最大并發(fā)量
NSOperationQueue是并發(fā)隊(duì)列,maxConcurrentOperationCount表示最大的并發(fā)數(shù)方椎。
當(dāng)maxConcurrentOperationCount是1時(shí)聂抢,雖然NSOperationQueue對(duì)象是默認(rèn)并發(fā)的調(diào)度NSOperation對(duì)象,但實(shí)際上棠众,此時(shí)琳疏,NSOperationQueue對(duì)象是串行隊(duì)列有决。但是和GCD串行不同的是,依賴和優(yōu)先級(jí)因素會(huì)影響NSOperationQueue對(duì)象調(diào)度任務(wù)的順序空盼。添加NSOperation對(duì)象的順序不一定是調(diào)度的順序书幕。
// 創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 創(chuàng)建操作
NSBlockOperation *block1Op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block1Op -- begin");
[NSThread sleepForTimeInterval:3]; // 模擬耗時(shí)操作
NSLog(@"block1Op -- end");
}];
NSBlockOperation *block2Op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block2Op -- begin");
[NSThread sleepForTimeInterval:4]; // 模擬耗時(shí)操作
NSLog(@"block2Op -- end");
}];
queue.maxConcurrentOperationCount = 1; // 最大并發(fā)個(gè)數(shù)
[block1Op addDependency:block2Op];// 添加依賴
// block2Op.queuePriority = NSOperationQueuePriorityHigh ;
[queue addOperation:block1Op];
[queue addOperation:block2Op];
五、自定義NSOperation子類
我們可以定義串行和并發(fā)的2種類型的NSOperation子類揽趾。
相關(guān)概念
- 串行(非并發(fā))的情況
- 常見(jiàn)使用場(chǎng)景:和網(wǎng)絡(luò)相關(guān)台汇,比如圖片下載
- 使用步驟
- 實(shí)現(xiàn)init方法,初始化操作對(duì)象以及一些其他對(duì)象
- 重寫(xiě)main方法篱瞎,在里面實(shí)現(xiàn)想要執(zhí)行的方法
- 在main方法中苟呐,創(chuàng)建自動(dòng)釋放池,因?yàn)槿绻钱惒讲僮骼睿瑹o(wú)法訪問(wèn)主線程的自動(dòng)釋放池
- 經(jīng)常通過(guò)cancelled屬性檢查方法是否取消牵素,并且對(duì)取消的做出響應(yīng)
- 響應(yīng)取消事件
- 取消事件可以在任何時(shí)間發(fā)生
- 定期調(diào)用對(duì)象的isCancelled方法,如果返回“YES”澄者,則立即返回笆呆,不再執(zhí)行任務(wù)
isCancelled方法本身非常輕量級(jí),可以頻繁調(diào)用粱挡,沒(méi)有任何顯著的性能損失
- 位置調(diào)用
- 在執(zhí)行任何實(shí)際工作之前
- 在循環(huán)的每次迭代期間或者如果每次迭代相對(duì)較長(zhǎng)赠幕,較頻繁時(shí)至少調(diào)用一次
- 在代碼中相對(duì)容易中止操作的任何點(diǎn)
- 并發(fā)
- 重寫(xiě)方法
- 必需重寫(xiě)四個(gè)方法:start、asynchronous询筏、executing榕堰、finished
- start(必需):所有并發(fā)操作必須重寫(xiě)此方法,并需要使用自定義的實(shí)現(xiàn)替換默認(rèn)行為屈留。任何時(shí)候都不能調(diào)用父類的start方法局冰。 即不可使用super。重寫(xiě)的start方法負(fù)責(zé)以異步的方式啟動(dòng)一個(gè)操作灌危,無(wú)論是開(kāi)啟一個(gè)線程還是調(diào)用異步函數(shù),都可以在start方法中進(jìn)行碳胳。注意在開(kāi)始操作之前勇蝙,應(yīng)該在start中更新操作的執(zhí)行狀態(tài),因?yàn)橐oKVO的鍵路徑發(fā)送當(dāng)前操作的執(zhí)行狀態(tài)挨约,方便查看操作狀態(tài)味混。
- main(可選):在這個(gè)方法中,放置執(zhí)行給定任務(wù)所需的代碼诫惭。應(yīng)該定義一個(gè)自定義初始化方法翁锡,以便更容易創(chuàng)建自定義類的實(shí)例。當(dāng)如果定義了自定義的getter和setter方法夕土,必須確保這些方法可以從多個(gè)線程安全地調(diào)用馆衔。雖然可以在start方法中執(zhí)行任務(wù)瘟判,但使用此方法實(shí)現(xiàn)任務(wù)可以更清晰地分離設(shè)置和任務(wù)代碼,即在start方法中調(diào)用mian方法。注意:要定義獨(dú)立的自動(dòng)釋放池與別的線程區(qū)分開(kāi)角溃。
- isFinished(必需):表示是否已完成拷获。需要實(shí)現(xiàn)KVO通知機(jī)制。
- isAsynchronous(必需):默認(rèn)返回 NO 减细,表示非并發(fā)執(zhí)行匆瓜。并發(fā)執(zhí)行需要自定義并且返回 YES。后面會(huì)根據(jù)這個(gè)返回值來(lái)決定是否并發(fā)未蝌。
- isExecuting(必需):表示是否執(zhí)行中驮吱,需要實(shí)現(xiàn)KVO通知機(jī)制。
注意:自己創(chuàng)建自動(dòng)釋放池萧吠,異步操作無(wú)法訪問(wèn)主線程的自動(dòng)釋放池
使用
實(shí)現(xiàn)例子如下:
非并發(fā)的情況下需要重寫(xiě)main方法糠馆,并且最好添加一個(gè)init方法用于初始化數(shù)據(jù)。
+ (instancetype)downloaderOperationWithURLPath:(NSString *)urlPath completeBlock:(CompleteBlock)completeBlock{
WNNoCurrentOPration *op = [[WNNoCurrentOPration alloc] init];
op.urlPath = urlPath;
op.completeBlock = completeBlock;
return op;
}
// main一般只適合自定義非并發(fā)的,在里面實(shí)現(xiàn)想執(zhí)行的任務(wù)
- (void)main{
// 是異步的話 就會(huì)導(dǎo)致訪問(wèn)不到當(dāng)前的釋放池
@autoreleasepool {
NSLog(@"%s",__func__);
// 當(dāng)處于取消操作怎憋,不執(zhí)行任務(wù)功能
if (self.isCancelled) return;
// 下載圖片的耗時(shí)操作
NSURL *url = [NSURL URLWithString:self.urlPath];
NSData *data = [NSData dataWithContentsOfURL:url];
NSLog(@"已下載 %@",[NSThread currentThread]);
UIImage *image = [UIImage imageWithData:data];
// 主線程回調(diào)又碌,完成操作后通知調(diào)用方完成回調(diào)
dispatch_async(dispatch_get_main_queue(), ^{
if (self.completeBlock != nil) {
self.completeBlock(image);
}
});
}
}
六、GCD VS NSOperation
GCD是蘋果公司為多核的并行運(yùn)算提出的解決方案绊袋,會(huì)自動(dòng)利用更多的CPU內(nèi)核(比如雙核毕匀、四核),而NSOperation是基于GCD的面向?qū)ο蟮姆庋b癌别,擁有GCD的特性皂岔。GCD是將任務(wù)(block)添加到隊(duì)列(串行/并行/全局/主隊(duì)列),并且以同步/異步的方式執(zhí)行任務(wù)的函數(shù),而NSOperation將操作(一般是異步的任務(wù))添加到隊(duì)列(一般是并發(fā)隊(duì)列),就會(huì)執(zhí)行指定操作的函數(shù)展姐。
相對(duì)于NSThread或者是跨平臺(tái)的pthread而言躁垛,GCD和NSOperation都是自動(dòng)管理線程的生命周期,開(kāi)發(fā)者只要專注于具體任務(wù)邏輯,不需要編寫(xiě)任何線程管理相關(guān)的代碼圾笨。
GCD提供了一些NSOperation不具備的功能:延遲執(zhí)行教馆、一次性執(zhí)行、調(diào)度組擂达;NSOperation里提供了一些方便的操作:最大并發(fā)數(shù)土铺、 隊(duì)列的暫定/繼續(xù)、取消所有的操作板鬓、指定操作之間的依賴關(guān)系(GCD可以用同步實(shí)現(xiàn)功能)悲敷;
GCD是無(wú)法控制線程的最大并發(fā)數(shù)的,而NSOperation可以設(shè)置最大并發(fā)數(shù)俭令,可以靈活的根據(jù)需要限制線程的個(gè)數(shù)后德。因?yàn)殚_(kāi)辟線程需要消耗必要的資源。
何時(shí)使用GCD:
調(diào)度隊(duì)列(Dispatch queues)抄腔、分組(groups)瓢湃、信號(hào)量(semaphores)理张、柵欄(barriers)組成了一組基本的并發(fā)原語(yǔ)。對(duì)于一次性執(zhí)行箱季,或者簡(jiǎn)單地加快現(xiàn)有方法的速度涯穷,使用輕量級(jí)的GCD分派(dispatch)比使用NSOperation更方便。
何時(shí)使用NSOperation:
在特定隊(duì)列優(yōu)先級(jí)和服務(wù)質(zhì)量(用于表示工作對(duì)系統(tǒng)的性質(zhì)和重要性)下藏雏, 可以用一系列依賴來(lái)調(diào)度NSOperation對(duì)象 拷况。與在GCD隊(duì)列上調(diào)度的block不同,NSOperation可以被取消和查詢其操作狀態(tài)掘殴。通過(guò)子類化赚瘦,NSOperation可以關(guān)聯(lián)執(zhí)行結(jié)果,以供之后參考奏寨。
注意:NSOperation和GCD不是互斥的起意。
七、隊(duì)列VS線程VS任務(wù)
從思維導(dǎo)圖了解整個(gè)概況病瞳。
1. 隊(duì)列(queue)
隊(duì)列是先進(jìn)先出特征數(shù)據(jù)結(jié)構(gòu)揽咕。并且隊(duì)列只是負(fù)責(zé)任務(wù)的調(diào)度,而不負(fù)責(zé)任務(wù)的執(zhí)行套菜。亲善。
按照任務(wù)的調(diào)度方式可以分為串行隊(duì)列和并發(fā)隊(duì)列。特點(diǎn)總結(jié)如下:
- 串行隊(duì)列
- 一個(gè)接一個(gè)的調(diào)度任務(wù)
- 無(wú)論隊(duì)列中所指定的執(zhí)行任務(wù)是同步還是異步逗柴,都會(huì)等待前一個(gè)任務(wù)執(zhí)行完成后蛹头,再調(diào)度后面的任務(wù)。
- 并發(fā)隊(duì)列
- 可以同時(shí)調(diào)度多個(gè)任務(wù)戏溺。
- 如果當(dāng)前調(diào)度的任務(wù)是同步執(zhí)行的渣蜗,會(huì)等待任務(wù)執(zhí)行完成后,再調(diào)度后續(xù)的任務(wù)旷祸。
- 如果當(dāng)前調(diào)度的任務(wù)是異步執(zhí)行的耕拷,同時(shí)底層線程池有可用的線程資源,會(huì)再新的線程調(diào)度后續(xù)任務(wù)的執(zhí)行肋僧。
我們知道系統(tǒng)提供了2個(gè)隊(duì)列:主隊(duì)列和全局并發(fā)隊(duì)列兩種隊(duì)列斑胜。我們還可以自己創(chuàng)建隊(duì)列。
- 主隊(duì)列
- 特點(diǎn)
- 添加到主隊(duì)列中的任務(wù)嫌吠,都會(huì)在主線程中執(zhí)行。
- 專門用來(lái)在主線程上調(diào)度任務(wù)的隊(duì)列掺炭。
- 在主線程空閑時(shí)才會(huì)調(diào)度隊(duì)列中的任務(wù)在主線程執(zhí)行辫诅。
- 不會(huì)開(kāi)啟線程。
- 串行涧狮。
- 獲取
- 會(huì)隨著程序啟動(dòng)一起創(chuàng)建炕矮。
- 主隊(duì)列只需要獲取不用創(chuàng)建么夫。
- 主隊(duì)列是負(fù)責(zé)在主線程調(diào)度任務(wù)的。
- 特點(diǎn)
- 全局隊(duì)列
- 本質(zhì)是一個(gè)并發(fā)隊(duì)列肤视,由系統(tǒng)提供档痪,方便編程,可以不用創(chuàng)建就直接使用邢滑。
- 全局隊(duì)列是所有應(yīng)用程序共享的
- GCD的一種隊(duì)列
- 全局隊(duì)列沒(méi)有名字腐螟,但是并發(fā)隊(duì)列有名字。有名字可以便于查看系統(tǒng)日志
- 自定義隊(duì)列
- 有2種方式:串行困后、并發(fā)乐纸。
- 添加到自定義隊(duì)列中的任務(wù),都會(huì)自動(dòng)放在子線程中執(zhí)行摇予。
2. 線程(thread)
- 開(kāi)辟線程具有一定的資源開(kāi)銷汽绢,iOS系統(tǒng)下主要成本包括:內(nèi)核數(shù)據(jù)結(jié)構(gòu)(大約1KB)、棽啻鳎空間(子線程512KB宁昭、主線程1MB,也可以使用-setStackSize:設(shè)置酗宋,但必須是4K的倍數(shù)积仗,而且最小是16K),創(chuàng)建線程大約需要90毫秒
- 對(duì)于單核CPU本缠,同一時(shí)間CPU只能處理1條線程斥扛,即只有1條線程在執(zhí)行,多線程并發(fā)(同時(shí))執(zhí)行丹锹,其實(shí)是CPU快速地在多條線程之間調(diào)度(切換)稀颁,如果CPU調(diào)度線程的時(shí)間足夠快,就造成了多線程并發(fā)執(zhí)行的假象楣黍。
- 線程是CPU調(diào)度和分派且能獨(dú)立運(yùn)行基本單位匾灶。
- 線程執(zhí)行任務(wù),實(shí)際做事的功能單元租漂。
- 異步:開(kāi)辟新線程阶女。
3. 任務(wù)(task)
一定要分清隊(duì)列、線程和任務(wù)這三者的關(guān)系:隊(duì)列調(diào)度任務(wù)哩治,將任務(wù)添加對(duì)應(yīng)的線程上秃踩,然后任務(wù)是在線程中執(zhí)行。
任務(wù)的執(zhí)行分為同步和異步业筏。
- 同步
- 當(dāng)前任務(wù)未完成憔杨,不會(huì)執(zhí)行下個(gè)任務(wù)
- 不具備開(kāi)辟新線程能力
- 異步
- 當(dāng)前任務(wù)未完成,同樣可以執(zhí)行下一個(gè)任務(wù)
- 具備開(kāi)辟新線程能力蒜胖,但是不一定會(huì)開(kāi)辟線程消别。開(kāi)辟線程需要CPU等資源抛蚤,而系統(tǒng)資源有限。不可能開(kāi)辟無(wú)限個(gè)線程寻狂。