代碼塊勇劣,一個可以增強函數(shù)功能的Objective-C特性柠衍。你可以在運行著iOS(版本4以上)和OS X(版本10.6以上)的應(yīng)用程序中使用代碼塊再沧。并發(fā)性:如何讓現(xiàn)代設(shè)備同時執(zhí)行多個任務(wù)尺栖。
-
代碼塊
代碼塊對象(簡稱為代碼塊)是對C語言中函數(shù)的擴展。除了函數(shù)中的代碼补疑,代碼塊還包含變量綁定班利。代碼塊有時也被稱為閉包洞豁。
代碼塊包含兩種類型的綁定: 自動型與托管型。自動綁定使用的是棧中的內(nèi)存茸歧,而托管綁定是通過堆創(chuàng)建的倦炒。
因為代碼塊底層實際上是由C語言實現(xiàn)的,所以它們在各種以C作為基礎(chǔ)的語言內(nèi)都是有效的软瞎,包括Objective-C逢唤、C++以及Objective-C++。
代碼塊在Xcode的GCC和Clang工具中是有效的涤浇,但它不屬于ANSI的C語言標準鳖藕。
1.1 代碼塊和函數(shù)指針
代碼塊借鑒了函數(shù)指針的語法。所以如果你知道如何聲明函數(shù)指針只锭,也就知道了如何聲明一個代碼塊著恩。與函數(shù)指針類似,代碼塊具有以下特征:
① 返回類型可以手動聲明也可以由編譯器推導(dǎo)纹烹;
② 具有指定類型的參數(shù)列表页滚;
③ 擁有名稱召边。
函數(shù)指針的聲明與代碼塊的聲明十分類似:
完整的代碼塊的定義以及代碼塊的內(nèi)容:
所以铺呵,一個完整的代碼塊可以用如下關(guān)系式來表示它們:
<returntype> (^blockname)(list of arguments) = ^(arguments){body;};
① 使用代碼塊
② 直接使用代碼塊
③ 使用typedef關(guān)鍵字
④ 代碼塊和變量
代碼塊被聲明后會捕捉創(chuàng)建點時的狀態(tài)。代碼塊可以訪問函數(shù)用到的標準類型的變量:
全局變量隧熙,包括在封閉范圍內(nèi)聲明的本地靜態(tài)變量片挂。
全局函數(shù)(明顯不是真實的變量)。
封閉范圍內(nèi)的參數(shù)贞盯。
函數(shù)級別(即與代碼塊聲明時相同的級別)的__block變量音念。它們是可以修改的變量。
封閉范圍內(nèi)的非靜態(tài)變量會被獲取為常量躏敢。
Objective-C的實例變量闷愤。
代碼塊內(nèi)部的本地變量。
⑤ 本地變量
本地變量就是與代碼塊在同一范圍內(nèi)聲明的變量件余。
第二個NSLog語句輸出200的原因: 變量是本地的讥脐,代碼塊會在定義時復(fù)制并保存它們的狀態(tài)。
⑥ 全局變量
本地變量與代碼塊擁有相同的有效范圍啼器,你可以根據(jù)需要把變量標記為靜態(tài)的(全局的)旬渠。
⑦ 參數(shù)變量
代碼塊中的參數(shù)變量與函數(shù)中的參數(shù)變量具有同樣的作用。
⑧ __block變量
本地變量會被代碼塊作為常理獲取到端壳。如果你想修改它們的值告丢,必須將它們聲明為可修改的。
有些變量是無法聲明為__block類型的损谦。它們有兩個限制:
沒有長度可變的數(shù)組岖免;
沒有包含可變長度數(shù)組的結(jié)構(gòu)體岳颇。
⑨ 代碼塊內(nèi)部的本地變量
1.2 Objective-C 變量
代碼塊是Objective-C語言中的優(yōu)秀公民,你可以像使用其他對象一樣使用它觅捆。使用時會遇到的最大問題就是內(nèi)存管理赦役。在代碼塊中訪問Objective-C變量時必須小心。
以下規(guī)則能幫助你處理內(nèi)存管理栅炒。
① 如果引用了一個Objective-C對象掂摔,必須要保留它。
② 如果通過引用訪問了一個實例變量赢赊,要保留一次self(即執(zhí)行方法的對象)乙漓。
③ 如果通過數(shù)值訪問了一個實例變量,變量需要保留释移。
注意:如果在代碼塊內(nèi)直接訪問實例變量, 則需要保留一次self, 即執(zhí)行方法的對象叭披。如果在代碼塊內(nèi)間接訪問實例變量,則變量本身需要保留玩讳。
因為代碼塊是對象涩蜘,所以可以向它發(fā)送任何與內(nèi)存管理有關(guān)的消息。在C語言級別中熏纯,必須使用Block_cpoy()和Block_release()函數(shù)來適當(dāng)?shù)毓芾韮?nèi)存同诫。 -
并發(fā)性
用來運行Xcode的Mac電腦的處理器至少擁有兩個核心,也可能更多≌晾剑現(xiàn)在最新的iOS設(shè)備都是多核的误窖。這意味著你可以在同一時間進行多項任務(wù)。蘋果公司提供了多種可以利用多核特性的API秩贰。能夠在同一時間執(zhí)行多項任務(wù)的程序稱為并發(fā)的程序霹俺。
利用并發(fā)性最基礎(chǔ)的方法是使用POSIX線程來處理程序的不同部分使其能夠獨立執(zhí)行。POSIX線程擁有支持C語言和Objective-C的API毒费。編寫并發(fā)性程序需要創(chuàng)建多個線程丙唧,而編寫線程代碼是很有挑戰(zhàn)性的。因為線程是級別較低的API, 你必須手動管理觅玻。根據(jù)硬件和其他軟件運行的環(huán)境想际,需要的線程數(shù)量會發(fā)生變化。處理所有的線程是需要技巧的串塑。
為了減輕在多核上編程的負擔(dān)沼琉,蘋果公司引入了GCD。這個技術(shù)較少了線程管理的麻煩桩匪。如果想要使用GCD, 你需要提交代碼塊或函數(shù)作為線程來運行打瘪。GCD是一個系統(tǒng)級別的技術(shù),因此你可以在任意級別的代碼塊中使用它。GCD決定需要多少線程并安排它們運行的速度闺骚。因為它是運行在系統(tǒng)級別上的彩扔,所以可以平衡應(yīng)用程序所有內(nèi)容的加載,這樣可以提高計算機或設(shè)備的執(zhí)行效率僻爽。
2.1 同步
我們?nèi)绾卧谟啥嗪私M成的通路中管理交通呢虫碉?可以使用同步裝置,比如在通道入口立一個標記(flag)或一個互斥(mutex)胸梆。
注意:mutex是mutual exclusion的縮寫敦捧,它指的是確保兩個線程不會在同一時間進入臨界區(qū)。
OC提供了一個語言級別的關(guān)鍵字@synchronized碰镜。這個關(guān)鍵字擁有一個參數(shù)兢卵,通常這個對象是可以修改的。它可以確保不同的線程會連續(xù)地訪問臨界區(qū)的代碼绪颖。
nonatomic關(guān)鍵字修飾屬性秽荤,是為了提高性能,而如果用atomic修飾柠横,這樣設(shè)置代碼和變量會產(chǎn)生一些消耗窃款,它會比直接訪問更慢一些。
① 選擇性能
如果你只想讓一些代碼在后臺執(zhí)行牍氛,NSObject也提供了方法晨继。這些方法的名字中都有performSelector:, 最簡單的就是performSelectorInBackground:withObject:了,它能在后臺執(zhí)行一個方法糜俗。它通過創(chuàng)建一個線程來運行方法踱稍。定義這些方法時必須遵從以下限制曲饱。
1.這些方法運行在各自的線程里悠抹,因此你必須為這些Cocoa對象創(chuàng)建一個自動釋放池,而主自動釋放池是與主線程相關(guān)的扩淀。
2.這些方法不能有返回值楔敌,并且要么沒有參數(shù),要么只有一個參數(shù)對象驻谆。換句話說卵凑,你只能使用以下代碼格式中的一種。
代碼如下:
當(dāng)方法執(zhí)行結(jié)束后胜臊,OC運行時會特地清理并棄掉線程勺卢。需要注意:方法執(zhí)行結(jié)束后并不會通知你:這是比較簡單的代碼。
② 調(diào)取隊列
GCD可以使用調(diào)度隊列(dispatch queue)象对。它與線程很相似但使用起來更簡單黑忱。只需寫下你的代碼,把它指派為一個隊列,系統(tǒng)就會執(zhí)行它了甫煞。你可以同步或異步執(zhí)行任意代碼菇曲。一共有以下3種類型的隊列。
連續(xù)隊列:每個連續(xù)隊列都會根據(jù)指派的順序執(zhí)行任務(wù)抚吠。你可以按自己的想法創(chuàng)建任意數(shù)量的隊列常潮,它們會并行操作任務(wù)。
并發(fā)隊列:每個并發(fā)隊列都能并發(fā)執(zhí)行一個或多個任務(wù)楷力。任務(wù)會根據(jù)指派到隊列的順序開始執(zhí)行喊式。你無法創(chuàng)建連續(xù)隊列,只能從系統(tǒng)提供的3個隊列內(nèi)選擇一個來使用萧朝。
主隊列:它是應(yīng)用程序中有效的主隊列垃帅,執(zhí)行的是應(yīng)用程序的主線程任務(wù)。
一剪勿、連續(xù)隊列
有時有一連串任務(wù)需要按照一定的順序執(zhí)行贸诚,這時便可以使用連續(xù)隊列。任務(wù)執(zhí)行順序為先入先出(FIFO): 只要任務(wù)是異步提交的厕吉,隊列會確保任務(wù)根據(jù)預(yù)定順序執(zhí)行酱固。這些隊列都是不會發(fā)生死鎖的。
死鎖(deadlock)是一個令人不悅的情況头朱,指的是兩個或多個任務(wù)在等待他方運行結(jié)束运悲。
二、并發(fā)隊列
并發(fā)隊列適用于那些可以并行運行的任務(wù)项钮。并發(fā)隊列也遵從先入先出(FIFO)的規(guī)范班眯,且任務(wù)可以在前一個任務(wù)結(jié)束前就開始執(zhí)行。一次所運行的任務(wù)數(shù)量是無法預(yù)測的烁巫,它會根據(jù)其他運行的任務(wù)在不同時間變化署隘。所以每次你運行同一個程序,并發(fā)任務(wù)的數(shù)量可能會是不一樣的亚隙。
注意:如果需要確保每次運行的任務(wù)數(shù)量都是一樣的磁餐,可以通過線程API來手動管理線程。
每個應(yīng)用程序都有3種并發(fā)隊列可以使用:高優(yōu)先級(high)阿弃、默認優(yōu)先級(default)和低優(yōu)先級(low)诊霹。
三、主隊列
因為這個隊列與主線程有關(guān)渣淳,所以必須小心安排這個隊列中的任務(wù)順序脾还,否則它們可能會阻塞主應(yīng)用程序運行。通常要以同步方式使用這個隊列入愧,提交多個任務(wù)并在它們操作完畢后執(zhí)行一些動作鄙漏。
四赛蔫、獲取當(dāng)前隊列
2.2 隊列也有內(nèi)存管理
調(diào)度隊列是引用計數(shù)對象∧嗾牛可以使用dispatch_retain()和dispatch_release()來修改隊列的保留計數(shù)器的值呵恢。它們與一般對象的retain和release語句類似。你只能對你自己創(chuàng)建的隊列使用這些函數(shù)媚创,而無法用在全局調(diào)度隊列上渗钉。事實上,如果你向全局隊列發(fā)送這些消息钞钙,它們會直接被忽略掉鳄橘,所以即使這樣做也是無害的。如果你編寫的是一個使用了垃圾回收的OSX應(yīng)用程序芒炼,那么你必須手動管理這些隊列瘫怜。
2.2.1 隊列的上下文
你可以向調(diào)度對象(包括調(diào)度隊列)指派全局數(shù)據(jù)上下文,可以在上下文中指派任意類型的數(shù)據(jù)本刽,比如OC對象或指針鲸湃。系統(tǒng)只能知道上下文包含了與隊列有關(guān)的數(shù)據(jù),上下文數(shù)據(jù)的內(nèi)存管理只能由你來做子寓。你必須在需要它的時候分配內(nèi)存并在隊列銷毀之前進行清理暗挑。在為上下文數(shù)據(jù)分配內(nèi)存的時候,可以使用dispatch_set_context()和dispatch_get_context()函數(shù)斜友。
① 清理函數(shù)
設(shè)置完上下文對象的數(shù)據(jù)之后炸裆,什么時候清理呢?你不需要真得知道上下文對象在何時何地會被棄用鲜屏。如果想解決上下文對象的清理問題烹看,你可以讓對象在它棄用的時候調(diào)用一個函數(shù),就像類里面的dealloc函數(shù)洛史。函數(shù)的格式應(yīng)該如下所示惯殊。(道理是一樣的,不需要知道它何時何地棄用或者銷毀虹菲,但只需要在它棄用或者銷毀的函數(shù)里進行相應(yīng)內(nèi)存管理的處理即可靠胜。)
② 添加任務(wù)
有兩種方式可以向隊列中添加任務(wù)
同步:隊列會一直等待前面任務(wù)結(jié)束掉瞳。
異步:添加任務(wù)后毕源,不必等待任務(wù),函數(shù)會立刻返回陕习。推薦優(yōu)先使用這種方式霎褐,因為它不會阻塞其他代碼的運行。
你可以選擇向隊列提交代碼塊或函數(shù)该镣。一共有4個調(diào)度函數(shù)冻璃,分別是代碼塊和函數(shù)各自的同步與異步方式。
注意:如果想要避免出現(xiàn)死鎖,那么絕對不要給運行在同一隊列中的任務(wù)調(diào)用dispatch_sync或dispatch_sync_f函數(shù)省艳。
2.2.2 調(diào)度程序
添加任務(wù)最簡單的方法就是通過代碼塊娘纷。
2.3 操作隊列
被稱為操作的API, 可以讓隊列在OC層級上使用起來更加簡單。
如果想要使用操作跋炕,首先需要創(chuàng)建一個操作對象赖晶,然后將其指派給操作隊列,并讓隊列執(zhí)行它辐烂。一共有3種創(chuàng)建操作的方式遏插。
① NSInvocationOperation: 如果你已經(jīng)擁有一個可以完成工作的類,并且想要在隊列上執(zhí)行纠修,可以嘗試使用這方法胳嘲。
② NSBlockOperation: 這有些像包含了需要執(zhí)行代碼塊的dispatch_async函數(shù)。
③ 自定義的操作:如果你需要更靈活的操作類型扣草,可以創(chuàng)建自己的自定義類型了牛。你必須通過NSOperation子類來定義你的操作。
創(chuàng)建調(diào)用操作(invocation operation)
創(chuàng)建代碼塊操作
向隊列中添加操作
一旦創(chuàng)建了操作辰妙,你就需要向隊列中添加代碼塊白魂。這次我們將使用NSOperationQueue來取代之前使用的dispatch_queue_t函數(shù)。NSOperationQueue一般會并發(fā)執(zhí)行操作上岗。它具有相關(guān)性福荸,因此如果某操作是基于其他操作的,它們會相應(yīng)地執(zhí)行肴掷。
如果要確保你的操作是連續(xù)執(zhí)行的敬锐,可以設(shè)置最大并發(fā)操作數(shù)為1,這樣任務(wù)將會按照先入先出的規(guī)范執(zhí)行呆瞻。在向隊列添加操作之前台夺,需要某個方法來引用到那個隊列。你可以創(chuàng)建一個新隊列或使用之前已經(jīng)定義過的隊列痴脾。
小結(jié):代碼塊是OC的新特性颤介,增強了函數(shù)的功能。有了代碼塊赞赖,就可以通過綁定變量來創(chuàng)建程序中會使用到的對象滚朵。代碼塊在實現(xiàn)并發(fā)性功能時尤其方便。
并發(fā)性很復(fù)雜前域,本章僅討論對OSX和iOS程序有效的并發(fā)性功能辕近。
蘋果公司的GCD特性提供了一種方法,你無需花很多時間在系統(tǒng)的低層級編碼匿垄,應(yīng)用程序就可以使用并發(fā)性移宅。你應(yīng)該多嘗試GCD和其他并發(fā)性編程功能归粉,以找出哪些對于你的應(yīng)用程序是可行的,哪些很好用漏峰。
隨著你的水平不斷提高以及蘋果公司添加更多的工具糠悼,你的應(yīng)用程序?qū)⒛軌虿⑿袌?zhí)行更多的任務(wù),從而更快地做出響應(yīng)浅乔。不過绢掰,一旦超過了臨界點,給應(yīng)用程序添加并行的任務(wù)就會得不償失童擎。(花大量時間編碼和調(diào)試)
如果你經(jīng)常要使用并發(fā)任務(wù)滴劲,請避免發(fā)生死鎖。(任務(wù)相互關(guān)聯(lián)導(dǎo)致程序永遠無法結(jié)束)或出現(xiàn)其他麻煩的Bug顾复。