瞭解IOS Block

OC中多線程編程的核心就是block與gcd。這雖然是兩種不同的技術,但他們是一併引入的。block是一種可在C、C++及OC代碼中使用的“詞法閉包”(lexical closure),它極爲有用,藉此機制,開發者可將代碼像對象一樣傳遞,令其在不同環境(context)下運行。在block的範圍內,它可以訪問到其中的全部變量。

    gcd是一種與block有關的技術,它提供了對線程的抽象,而這種抽象基於“派發隊列”(dispatch queue)。開發者可將block排入隊列中,有gcd負責處理所有調度事宜。gcd會根據系統資源情況,適時得創建、複用、摧毀後臺線程,以便處理每個隊列。此外,使用GCD還可以方便的完成常見編程任務,比如編寫“只執行一次的線程安全代碼”(thread-safe single-code execution),或者根據可用的系統資源來併發執行多個操作。

    block和gcd是當前OC的編程基石。因此必須理解其工作原理及功能

第37條:理解block這一概念

    block可以實現閉包。這項語言特性是作爲擴展而加入GCC編譯器中的,在近期版本的Clang中都可以使用。從技術上講,這是位於C語言層面的特性,因此只要有支持此特性的編譯器以及能執行block的運行期組件,就可以在C、C++、OC,PC++代碼中使用它。

block的基礎知識

    block其實就是個值,而且自有其相關類型。語法和函數指針近似
[objc] view plain copy
  1. void (^someBlock)() = ^{  
  2.     //Block implementation here  
  3. };  
    block的強大之處是:在聲明它的範圍內,所有變量都可以爲其所捕獲。例如下面代碼:
[objc] view plain copy
  1. int additional = 5;  
  2. int(^addBlock)(int a ,int b) = ^(int a,int b){  
  3.     return a+b+additional;  
  4. };  
  5. int add = addBlock(2,5);//add = 12  
    默認情況下,爲block捕獲的變量,是不可以修改的。如果在block中需要修改需要使用__block修飾符,修飾變量。
[objc] view plain copy
  1. NSArray *array = @[@0,@1,@2,@3,@4,@5];  
  2. __block NSInteger count =0;  
  3. [array enumerateObjectUsingBlock:^(NSNumber* number,NSUInteger idx, BOOLBOOL *stop){  
  4.     if([number compare:@2]==NSOrderAscending){  
  5.         count++;  
  6.     }  
  7. }];  
  8. //count =2  
    block中直接使用count的值。如果block捕獲的變量是對象類型,那麼就會自動保留它。系統在釋放這個block的時候也會將其一併釋放,block本身也是變量,有引用計數。
    block還可以使用self變量。block總能修改實例變量,所以在聲明時無需加__block。但是self卻被保留了。如果self所指代的哪個對象同時頁保留了塊,那麼這種情況就會導致“循環引用”。更多見本章40條

block的內部結構

    每個OC對象都佔據者某個內存區域。block本身也是對象,在存放對象的內存區域中,首個變量是指向Class對象的指針,該指針也叫isa。其餘內存裏含有block對象正常運轉所需的各種信息,block對象的內部實現參考點擊打開鏈接 如圖:

    在內存佈局中,最重要的是invoke變量,這是個函數指針,指向block的實現代碼。descriptor變量是指向結構體的指針,每個block都包含了此結構體,其中聲明瞭block對象的總體大小,還聲明瞭copy與dispose兩個輔助番薯所對應的函數指針。前者是保留捕獲的對象,後者則將之釋放。
    block還會把它所捕獲的所有變量都拷貝一份。這些拷貝放到descriptor變量後面,捕獲了多少對象,就要佔據多少內存。請注意,拷貝的並不是對象本身,而是指向這些變量的指針變量。invoke函數爲何要把block對象作爲參數傳進來呢?原因就在於,執行block時,要從內存中把這些捕獲的變量讀出來。
全局block,棧block,堆block
   定義block的時候,是分配在棧上的,block只在定義它的範圍有效。下面的寫法很危險:
[objc] view plain copy
  1. void (^block)();  
  2. if(/*some condition*/){  
  3.     block = ^{NSLog(@"Block A");};  
  4. }else {  
  5.     block = ^{NSLog(@"Block B");};  
  6. }  
  7. block();  
    定義在if-else中的兩個block都是在棧上的,作用範圍只限於兩個大括號之內。所以上述可能運行正確,可能錯誤。解決這個問題的辦法是給block對象發送copy消息。這樣就可以把block複製到堆上了。修改後代碼:
[objc] view plain copy
  1. void (^block)();  
  2. if(/*some condition*/){  
  3.     block = [^{NSLog(@"Block A");} copy];  
  4. }else {  
  5.     block = [^{NSLog(@"Block B");}copy];  
  6. }  
  7. block();  
採用手動計數的,需要將其release,採用ARC則不用。
    除了“棧block”、“堆block”之外,還有一類block叫做“全局Block”。這種block不會捕捉任何狀態(比如外圍的變量等),運行時也無需有狀態來參與。block所需要的整個內存區域,在編譯期已經完全確定了,因此,全局block可以聲明在全局內存裏,而不需要在每次用到的時候於棧中創建。另外,全局block的拷貝操作是一個空操作,因爲全局block絕不可能爲系統回收。這種block相當於單例。
更多block存儲區域參考點擊打開鏈接
【本節要點】
● block是C、C++、OC中的詞法閉包
● block可以接收參數、也可以返回值
● block可以在棧上、堆上、全局。分配在棧上的block可以拷貝到堆上。

第38條:爲常用的塊類型創建typedef

看兩個例子:
[objc] view plain copy
  1. //before  
  2. -(void)startWithCompletionHandler:(void (^) (NSData *data,NSError *error))completion;  
  3. //after  
  4. typedef void(^EOCCompletionHandler) (NSData *data,NSError *error);  
  5. -(void)startWithCompletionHandler:(EOCCompletionHandler)completion;  

第39條:用handler block降低代碼分散程度

【本節要點】
● 在創建對象時,可以使用內聯的handler block將相關業務邏輯一併聲明。
● 在有多個實例需要監視時,如果採用delegate模式,那麼經常需要根據傳入的對象來切換。而偌該用handler實現,則可直接將block與相關對象放在一起。
● 涉及API時如果用到了handler block,那麼可以增加一個參數,使調用者可通過此參數來決定鷹把block安排在哪個隊列上執行。

第40條:用block引用其所屬對象時不要出現循環引用

    在沒有出現block copy的情況下,是不會出現循環引用的。因爲:棧上的block,雖然引用了self,構成環形引用,但是,最終棧上的block是需要釋放的,這是一個出口。
1
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章