《Objective-C基礎(chǔ)教程》讀書(shū)筆記17—代碼塊和并發(fā)性

代碼塊,一個(gè)可以增強(qiáng)函數(shù)功能的Objective-C特性。你可以在運(yùn)行著iOS(版本4以上)和OS X(版本10.6以上)的應(yīng)用程序中使用代碼塊。并發(fā)性:如何讓現(xiàn)代設(shè)備同時(shí)執(zhí)行多個(gè)任務(wù)。

  1. 代碼塊
    代碼塊對(duì)象(簡(jiǎn)稱為代碼塊)是對(duì)C語(yǔ)言中函數(shù)的擴(kuò)展。除了函數(shù)中的代碼,代碼塊還包含變量綁定。代碼塊有時(shí)也被稱為閉包。
    代碼塊包含兩種類型的綁定: 自動(dòng)型與托管型。自動(dòng)綁定使用的是棧中的內(nèi)存,而托管綁定是通過(guò)堆創(chuàng)建的。
    因?yàn)榇a塊底層實(shí)際上是由C語(yǔ)言實(shí)現(xiàn)的,所以它們?cè)诟鞣N以C作為基礎(chǔ)的語(yǔ)言內(nèi)都是有效的,包括Objective-C、C++以及Objective-C++。
    代碼塊在Xcode的GCC和Clang工具中是有效的,但它不屬于ANSI的C語(yǔ)言標(biāo)準(zhǔn)。
    1.1 代碼塊和函數(shù)指針
    代碼塊借鑒了函數(shù)指針的語(yǔ)法。所以如果你知道如何聲明函數(shù)指針,也就知道了如何聲明一個(gè)代碼塊。與函數(shù)指針類似,代碼塊具有以下特征:
    ① 返回類型可以手動(dòng)聲明也可以由編譯器推導(dǎo);
    ② 具有指定類型的參數(shù)列表;
    ③ 擁有名稱。
    函數(shù)指針的聲明與代碼塊的聲明十分類似:


    image.png

    完整的代碼塊的定義以及代碼塊的內(nèi)容:


    image.png

    image.png

    所以,一個(gè)完整的代碼塊可以用如下關(guān)系式來(lái)表示它們:
    <returntype> (^blockname)(list of arguments) = ^(arguments){body;};
    ① 使用代碼塊
    image.png

    image.png

    ② 直接使用代碼塊
    image.png

    image.png

    ③ 使用typedef關(guān)鍵字
    image.png

    image.png

    ④ 代碼塊和變量
    代碼塊被聲明后會(huì)捕捉創(chuàng)建點(diǎn)時(shí)的狀態(tài)。代碼塊可以訪問(wèn)函數(shù)用到的標(biāo)準(zhǔn)類型的變量:
    全局變量,包括在封閉范圍內(nèi)聲明的本地靜態(tài)變量。
    全局函數(shù)(明顯不是真實(shí)的變量)。
    封閉范圍內(nèi)的參數(shù)。
    函數(shù)級(jí)別(即與代碼塊聲明時(shí)相同的級(jí)別)的__block變量。它們是可以修改的變量。
    封閉范圍內(nèi)的非靜態(tài)變量會(huì)被獲取為常量。
    Objective-C的實(shí)例變量。
    代碼塊內(nèi)部的本地變量。
    ⑤ 本地變量
    本地變量就是與代碼塊在同一范圍內(nèi)聲明的變量。


    image.png

    image.png

    第二個(gè)NSLog語(yǔ)句輸出200的原因: 變量是本地的,代碼塊會(huì)在定義時(shí)復(fù)制并保存它們的狀態(tài)。
    ⑥ 全局變量
    本地變量與代碼塊擁有相同的有效范圍,你可以根據(jù)需要把變量標(biāo)記為靜態(tài)的(全局的)。
    image.png

    image.png

    ⑦ 參數(shù)變量
    代碼塊中的參數(shù)變量與函數(shù)中的參數(shù)變量具有同樣的作用。
    ⑧ __block變量
    本地變量會(huì)被代碼塊作為常理獲取到。如果你想修改它們的值,必須將它們聲明為可修改的。
    image.png

    image.png

    有些變量是無(wú)法聲明為_(kāi)_block類型的。它們有兩個(gè)限制:
    沒(méi)有長(zhǎng)度可變的數(shù)組;
    沒(méi)有包含可變長(zhǎng)度數(shù)組的結(jié)構(gòu)體。
    ⑨ 代碼塊內(nèi)部的本地變量
    image.png

    image.png

    1.2 Objective-C 變量
    代碼塊是Objective-C語(yǔ)言中的優(yōu)秀公民,你可以像使用其他對(duì)象一樣使用它。使用時(shí)會(huì)遇到的最大問(wèn)題就是內(nèi)存管理。在代碼塊中訪問(wèn)Objective-C變量時(shí)必須小心。
    以下規(guī)則能幫助你處理內(nèi)存管理。
    ① 如果引用了一個(gè)Objective-C對(duì)象,必須要保留它。
    ② 如果通過(guò)引用訪問(wèn)了一個(gè)實(shí)例變量,要保留一次self(即執(zhí)行方法的對(duì)象)。
    ③ 如果通過(guò)數(shù)值訪問(wèn)了一個(gè)實(shí)例變量,變量需要保留。
    注意:如果在代碼塊內(nèi)直接訪問(wèn)實(shí)例變量, 則需要保留一次self, 即執(zhí)行方法的對(duì)象。如果在代碼塊內(nèi)間接訪問(wèn)實(shí)例變量,則變量本身需要保留。
    因?yàn)榇a塊是對(duì)象,所以可以向它發(fā)送任何與內(nèi)存管理有關(guān)的消息。在C語(yǔ)言級(jí)別中,必須使用Block_cpoy()和Block_release()函數(shù)來(lái)適當(dāng)?shù)毓芾韮?nèi)存。
  2. 并發(fā)性
    用來(lái)運(yùn)行Xcode的Mac電腦的處理器至少擁有兩個(gè)核心,也可能更多?,F(xiàn)在最新的iOS設(shè)備都是多核的。這意味著你可以在同一時(shí)間進(jìn)行多項(xiàng)任務(wù)。蘋(píng)果公司提供了多種可以利用多核特性的API。能夠在同一時(shí)間執(zhí)行多項(xiàng)任務(wù)的程序稱為并發(fā)的程序。
    利用并發(fā)性最基礎(chǔ)的方法是使用POSIX線程來(lái)處理程序的不同部分使其能夠獨(dú)立執(zhí)行。POSIX線程擁有支持C語(yǔ)言和Objective-C的API。編寫(xiě)并發(fā)性程序需要?jiǎng)?chuàng)建多個(gè)線程,而編寫(xiě)線程代碼是很有挑戰(zhàn)性的。因?yàn)榫€程是級(jí)別較低的API, 你必須手動(dòng)管理。根據(jù)硬件和其他軟件運(yùn)行的環(huán)境,需要的線程數(shù)量會(huì)發(fā)生變化。處理所有的線程是需要技巧的。
    為了減輕在多核上編程的負(fù)擔(dān),蘋(píng)果公司引入了GCD。這個(gè)技術(shù)較少了線程管理的麻煩。如果想要使用GCD, 你需要提交代碼塊或函數(shù)作為線程來(lái)運(yùn)行。GCD是一個(gè)系統(tǒng)級(jí)別的技術(shù),因此你可以在任意級(jí)別的代碼塊中使用它。GCD決定需要多少線程并安排它們運(yùn)行的速度。因?yàn)樗沁\(yùn)行在系統(tǒng)級(jí)別上的,所以可以平衡應(yīng)用程序所有內(nèi)容的加載,這樣可以提高計(jì)算機(jī)或設(shè)備的執(zhí)行效率。
    2.1 同步
    我們?nèi)绾卧谟啥嗪私M成的通路中管理交通呢?可以使用同步裝置,比如在通道入口立一個(gè)標(biāo)記(flag)或一個(gè)互斥(mutex)。
    注意:mutex是mutual exclusion的縮寫(xiě),它指的是確保兩個(gè)線程不會(huì)在同一時(shí)間進(jìn)入臨界區(qū)。
    OC提供了一個(gè)語(yǔ)言級(jí)別的關(guān)鍵字@synchronized。這個(gè)關(guān)鍵字擁有一個(gè)參數(shù),通常這個(gè)對(duì)象是可以修改的。它可以確保不同的線程會(huì)連續(xù)地訪問(wèn)臨界區(qū)的代碼。
    nonatomic關(guān)鍵字修飾屬性,是為了提高性能,而如果用atomic修飾,這樣設(shè)置代碼和變量會(huì)產(chǎn)生一些消耗,它會(huì)比直接訪問(wèn)更慢一些。
    ① 選擇性能
    如果你只想讓一些代碼在后臺(tái)執(zhí)行,NSObject也提供了方法。這些方法的名字中都有performSelector:, 最簡(jiǎn)單的就是performSelectorInBackground:withObject:了,它能在后臺(tái)執(zhí)行一個(gè)方法。它通過(guò)創(chuàng)建一個(gè)線程來(lái)運(yùn)行方法。定義這些方法時(shí)必須遵從以下限制。
    1.這些方法運(yùn)行在各自的線程里,因此你必須為這些Cocoa對(duì)象創(chuàng)建一個(gè)自動(dòng)釋放池,而主自動(dòng)釋放池是與主線程相關(guān)的。
    2.這些方法不能有返回值,并且要么沒(méi)有參數(shù),要么只有一個(gè)參數(shù)對(duì)象。換句話說(shuō),你只能使用以下代碼格式中的一種。
    代碼如下:


    1542597050043.jpg

    image.png

    當(dāng)方法執(zhí)行結(jié)束后,OC運(yùn)行時(shí)會(huì)特地清理并棄掉線程。需要注意:方法執(zhí)行結(jié)束后并不會(huì)通知你:這是比較簡(jiǎn)單的代碼。
    ② 調(diào)取隊(duì)列
    GCD可以使用調(diào)度隊(duì)列(dispatch queue)。它與線程很相似但使用起來(lái)更簡(jiǎn)單。只需寫(xiě)下你的代碼,把它指派為一個(gè)隊(duì)列,系統(tǒng)就會(huì)執(zhí)行它了。你可以同步或異步執(zhí)行任意代碼。一共有以下3種類型的隊(duì)列。
    連續(xù)隊(duì)列:每個(gè)連續(xù)隊(duì)列都會(huì)根據(jù)指派的順序執(zhí)行任務(wù)。你可以按自己的想法創(chuàng)建任意數(shù)量的隊(duì)列,它們會(huì)并行操作任務(wù)。
    并發(fā)隊(duì)列:每個(gè)并發(fā)隊(duì)列都能并發(fā)執(zhí)行一個(gè)或多個(gè)任務(wù)。任務(wù)會(huì)根據(jù)指派到隊(duì)列的順序開(kāi)始執(zhí)行。你無(wú)法創(chuàng)建連續(xù)隊(duì)列,只能從系統(tǒng)提供的3個(gè)隊(duì)列內(nèi)選擇一個(gè)來(lái)使用。
    主隊(duì)列:它是應(yīng)用程序中有效的主隊(duì)列,執(zhí)行的是應(yīng)用程序的主線程任務(wù)。
    一、連續(xù)隊(duì)列
    有時(shí)有一連串任務(wù)需要按照一定的順序執(zhí)行,這時(shí)便可以使用連續(xù)隊(duì)列。任務(wù)執(zhí)行順序?yàn)橄热胂瘸?FIFO): 只要任務(wù)是異步提交的,隊(duì)列會(huì)確保任務(wù)根據(jù)預(yù)定順序執(zhí)行。這些隊(duì)列都是不會(huì)發(fā)生死鎖的。
    死鎖(deadlock)是一個(gè)令人不悅的情況,指的是兩個(gè)或多個(gè)任務(wù)在等待他方運(yùn)行結(jié)束。


    image.png

    二、并發(fā)隊(duì)列
    并發(fā)隊(duì)列適用于那些可以并行運(yùn)行的任務(wù)。并發(fā)隊(duì)列也遵從先入先出(FIFO)的規(guī)范,且任務(wù)可以在前一個(gè)任務(wù)結(jié)束前就開(kāi)始執(zhí)行。一次所運(yùn)行的任務(wù)數(shù)量是無(wú)法預(yù)測(cè)的,它會(huì)根據(jù)其他運(yùn)行的任務(wù)在不同時(shí)間變化。所以每次你運(yùn)行同一個(gè)程序,并發(fā)任務(wù)的數(shù)量可能會(huì)是不一樣的。
    注意:如果需要確保每次運(yùn)行的任務(wù)數(shù)量都是一樣的,可以通過(guò)線程API來(lái)手動(dòng)管理線程。
    每個(gè)應(yīng)用程序都有3種并發(fā)隊(duì)列可以使用:高優(yōu)先級(jí)(high)、默認(rèn)優(yōu)先級(jí)(default)和低優(yōu)先級(jí)(low)。
    image.png

    三、主隊(duì)列
    image.png

    因?yàn)檫@個(gè)隊(duì)列與主線程有關(guān),所以必須小心安排這個(gè)隊(duì)列中的任務(wù)順序,否則它們可能會(huì)阻塞主應(yīng)用程序運(yùn)行。通常要以同步方式使用這個(gè)隊(duì)列,提交多個(gè)任務(wù)并在它們操作完畢后執(zhí)行一些動(dòng)作。
    四、獲取當(dāng)前隊(duì)列
    image.png

    2.2 隊(duì)列也有內(nèi)存管理
    調(diào)度隊(duì)列是引用計(jì)數(shù)對(duì)象??梢允褂胐ispatch_retain()和dispatch_release()來(lái)修改隊(duì)列的保留計(jì)數(shù)器的值。它們與一般對(duì)象的retain和release語(yǔ)句類似。你只能對(duì)你自己創(chuàng)建的隊(duì)列使用這些函數(shù),而無(wú)法用在全局調(diào)度隊(duì)列上。事實(shí)上,如果你向全局隊(duì)列發(fā)送這些消息,它們會(huì)直接被忽略掉,所以即使這樣做也是無(wú)害的。如果你編寫(xiě)的是一個(gè)使用了垃圾回收的OSX應(yīng)用程序,那么你必須手動(dòng)管理這些隊(duì)列。
    2.2.1 隊(duì)列的上下文
    你可以向調(diào)度對(duì)象(包括調(diào)度隊(duì)列)指派全局?jǐn)?shù)據(jù)上下文,可以在上下文中指派任意類型的數(shù)據(jù),比如OC對(duì)象或指針。系統(tǒng)只能知道上下文包含了與隊(duì)列有關(guān)的數(shù)據(jù),上下文數(shù)據(jù)的內(nèi)存管理只能由你來(lái)做。你必須在需要它的時(shí)候分配內(nèi)存并在隊(duì)列銷毀之前進(jìn)行清理。在為上下文數(shù)據(jù)分配內(nèi)存的時(shí)候,可以使用dispatch_set_context()和dispatch_get_context()函數(shù)。
    image.png

    ① 清理函數(shù)

    設(shè)置完上下文對(duì)象的數(shù)據(jù)之后,什么時(shí)候清理呢?你不需要真得知道上下文對(duì)象在何時(shí)何地會(huì)被棄用。如果想解決上下文對(duì)象的清理問(wèn)題,你可以讓對(duì)象在它棄用的時(shí)候調(diào)用一個(gè)函數(shù),就像類里面的dealloc函數(shù)。函數(shù)的格式應(yīng)該如下所示。(道理是一樣的,不需要知道它何時(shí)何地棄用或者銷毀,但只需要在它棄用或者銷毀的函數(shù)里進(jìn)行相應(yīng)內(nèi)存管理的處理即可。)


    image.png

    1542621894089.jpg

    ② 添加任務(wù)
    有兩種方式可以向隊(duì)列中添加任務(wù)
    同步:隊(duì)列會(huì)一直等待前面任務(wù)結(jié)束。
    異步:添加任務(wù)后,不必等待任務(wù),函數(shù)會(huì)立刻返回。推薦優(yōu)先使用這種方式,因?yàn)樗粫?huì)阻塞其他代碼的運(yùn)行。
    你可以選擇向隊(duì)列提交代碼塊或函數(shù)。一共有4個(gè)調(diào)度函數(shù),分別是代碼塊和函數(shù)各自的同步與異步方式。
    注意:如果想要避免出現(xiàn)死鎖,那么絕對(duì)不要給運(yùn)行在同一隊(duì)列中的任務(wù)調(diào)用dispatch_sync或dispatch_sync_f函數(shù)。
    2.2.2 調(diào)度程序
    添加任務(wù)最簡(jiǎn)單的方法就是通過(guò)代碼塊。
    image.png

    image.png

    image.png

    2.3 操作隊(duì)列
    被稱為操作的API, 可以讓隊(duì)列在OC層級(jí)上使用起來(lái)更加簡(jiǎn)單。
    如果想要使用操作,首先需要?jiǎng)?chuàng)建一個(gè)操作對(duì)象,然后將其指派給操作隊(duì)列,并讓隊(duì)列執(zhí)行它。一共有3種創(chuàng)建操作的方式。
    ① NSInvocationOperation: 如果你已經(jīng)擁有一個(gè)可以完成工作的類,并且想要在隊(duì)列上執(zhí)行,可以嘗試使用這方法。
    ② NSBlockOperation: 這有些像包含了需要執(zhí)行代碼塊的dispatch_async函數(shù)。
    ③ 自定義的操作:如果你需要更靈活的操作類型,可以創(chuàng)建自己的自定義類型。你必須通過(guò)NSOperation子類來(lái)定義你的操作。
    創(chuàng)建調(diào)用操作(invocation operation)
    image.png

    image.png

    創(chuàng)建代碼塊操作
    image.png

    image.png

    向隊(duì)列中添加操作
    一旦創(chuàng)建了操作,你就需要向隊(duì)列中添加代碼塊。這次我們將使用NSOperationQueue來(lái)取代之前使用的dispatch_queue_t函數(shù)。NSOperationQueue一般會(huì)并發(fā)執(zhí)行操作。它具有相關(guān)性,因此如果某操作是基于其他操作的,它們會(huì)相應(yīng)地執(zhí)行。
    如果要確保你的操作是連續(xù)執(zhí)行的,可以設(shè)置最大并發(fā)操作數(shù)為1,這樣任務(wù)將會(huì)按照先入先出的規(guī)范執(zhí)行。在向隊(duì)列添加操作之前,需要某個(gè)方法來(lái)引用到那個(gè)隊(duì)列。你可以創(chuàng)建一個(gè)新隊(duì)列或使用之前已經(jīng)定義過(guò)的隊(duì)列。
    image.png

    小結(jié):代碼塊是OC的新特性,增強(qiáng)了函數(shù)的功能。有了代碼塊,就可以通過(guò)綁定變量來(lái)創(chuàng)建程序中會(huì)使用到的對(duì)象。代碼塊在實(shí)現(xiàn)并發(fā)性功能時(shí)尤其方便。
    并發(fā)性很復(fù)雜,本章僅討論對(duì)OSX和iOS程序有效的并發(fā)性功能。
    蘋(píng)果公司的GCD特性提供了一種方法,你無(wú)需花很多時(shí)間在系統(tǒng)的低層級(jí)編碼,應(yīng)用程序就可以使用并發(fā)性。你應(yīng)該多嘗試GCD和其他并發(fā)性編程功能,以找出哪些對(duì)于你的應(yīng)用程序是可行的,哪些很好用。
    隨著你的水平不斷提高以及蘋(píng)果公司添加更多的工具,你的應(yīng)用程序?qū)⒛軌虿⑿袌?zhí)行更多的任務(wù),從而更快地做出響應(yīng)。不過(guò),一旦超過(guò)了臨界點(diǎn),給應(yīng)用程序添加并行的任務(wù)就會(huì)得不償失。(花大量時(shí)間編碼和調(diào)試)
    如果你經(jīng)常要使用并發(fā)任務(wù),請(qǐng)避免發(fā)生死鎖。(任務(wù)相互關(guān)聯(lián)導(dǎo)致程序永遠(yuǎn)無(wú)法結(jié)束)或出現(xiàn)其他麻煩的Bug。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容