iOS - Blocks

 

iOS中Blocks的介紹

 
 

1. 什麼是Blockshtml

      Blocks是C語言的擴充功能。就是:帶有自動變量的匿名函數。數組

      相似C語言的函數指針。但Blocks不是一個指針,而是一個不帶名字的函數,它容許開發者在兩個對象之間將任意的語句看成數據進行傳遞,同時它又能得到上下文的信息(閉包Closure),因此在一些場合使用Blocks很方便簡潔。xcode

 

2. Block語法網絡

      咱們先來看一個例子吧。數據結構

      ^int(int count){return count++;}閉包

      這個Block很是簡單,就是把計數器加一,但麻雀雖小,五臟俱全,語法上一個元素都沒漏掉。首先是^符號(插入符號caret),而後依次是是返回值類型,參數列表,表達式。app

      ^            int                  (int count)              {return count++;}ide

    caret    返回參數          參數列表               表達式svn

      齊全的Block就是這些內容了,不一樣的Block的表達式的複雜程度各異,但元素就是這麼多了。函數

      不過不少時候咱們會遇到沒有返回參數,或者沒有傳入參數,甚至既沒有傳入參數也沒有返回參數的狀況。這個時候Block能夠省略相關的內容,就是說相對應的那一塊不用寫了。好比:

      沒有返回參數,可能就會寫成:^(int count){printf("count=%d", count);}

      沒有參數列表,可能會寫成:^int{return 1;}

      既沒有返回參數也沒有參數列表,可能就會寫成:^{printf("Blocks Demo!\n")};

 

      Block還能夠申明變量,Block變量同樣能夠用typedef來定義成新的類型,這種作法下和函數指針真的很是很是相似,僅僅是將*換成了^。舉個例子:

      int (^blk)(int) = ^int (int count) {return count+;};

      熟悉C語言的人對這個都會比較熟悉。這裏有一個要說明,上面的賦值語句右側能夠省略掉返回類型(猜想是這部分信息編譯器已經能夠肯定,因此再也不是必須提供的了)。這樣,上面的語句也能夠寫成;

       int (^blk)(int) = ^(int count) {return count+;};

      若是使用typedef,就能夠更清晰一點:

      typedef int (^blk_t)(int);

      blk_t blk =  ^(int count) {return count+;};

 

3. 截獲自動變量值

      Block是帶自動變量的匿名函數,下面就要看看「自動變量」了。Block能夠訪問在它以前申明的變量,但也有它特殊的地方,先看一個例子:  

  1.     int val = 1;  
  2.     void (^blk)(void) = ^{printf("val=%d\n", val);};  
  3.     val = 2;  
  4.     blk();  Block在定義時至關於對val這個變量照了張相,
  5.     return 0;  

      運行結果val=1。請注意,雖然這個時候val的值已經變成了2,但Block裏面仍然是1,也就是說,Block在定義時至關於對val這個變量照了張相,而後一直本身使用這張相片,無論val自己何去何從。

      這個特性能夠帶來很大的便利(記下了定義時的上下文),有時是咱們所須要的;但一不當心也很容易錯,因此使用時須要注意。Block裏面,val就是隻讀的,並且值是定義時的那個。

      雖然記住上下文,Block的內容也跟隨改變或者要修改自動變量的值,這個就須要用到__block關鍵詞了。繼續上代碼:

  1.     __block int val = 1;  
  2.     void (^blk)(void) = ^{printf("val=%d\n", val);};  
  3.       
  4.     val = 2;  
  5.     blk();  
  6.     return 0;  
  7. }  

      這段代碼和上一段的區別僅僅是多了__block的聲明,但運行結果就是val=2了,也就是說,Block能跟蹤val的變化了。

 

      這時,Block裏面也能夠改變val的值了,就是說,用了__block以後,val對於Block再也不是隻讀的了,而是和本身定義的變量同樣了。

      在一個Block裏面,每每兩種變量都須要有,具體怎麼使用,就看具體的狀況了。

 

4. Block的使用

      我老是以爲任何一種技術的出現老是用來解決某個問題的,也決定了在何種狀況下使用該技術。Block應該如何使用呢?這是仁者見仁,智者見智的問題了,我接觸最多的是在GCD裏面。

      我的的感受這個東東主要用在回調裏面,好比:網絡鏈接成功後我應該把某個按鈕激活之類的,Block使用起來簡潔明快,如魚得水。GCD實際上也是給出了系統的回調,因此就特別適合Block大顯身手。

 

5. 其餘

      Block固然還有其餘的一些內容,好比能夠做爲函數參數來傳遞,好比當自動變量是ObjC的對象時,雖然不能修改,但能夠調用對象的方法,再好比,C語言的數組不能做爲自動變量等等。但這些都不是Block主要的內容,最重要的仍是靈活的使用。

 

 

 

深刻Blocks分析

1.簡介

      從iOS4開始,蘋果引入了這個C語言的擴充功能「Blocks」,在一些特定的場景下也是一把利刃。我前面一篇博客中初步介紹了Blocks這個東西,主要是語法的介紹(《iOS中Blocks的介紹》)。

      我曾經看見了老外的一個系列的Blocks介紹,頗有深度(A look inside blocks:Episode 1A look inside blocks:Episode 2, A look inside blocks:Episode 3),裏面深刻到彙編的層次對Blocks的實現進行了分析。不過若是象我這樣對於彙編不熟悉的人確定也是不少的,理解起來十分痛苦,因而就想到從ObjC自己對Blocks進行的處理裏面來入手分析,看看對於Blocks都悄悄作了什麼。

2.環境

      很簡單,就是Xcode啦。使用的編譯器是CLang,主要是利用了-rewrite-objc這個參數,把源文件轉換成中間文件。這樣就揭開了面紗的一角。我使用的clang編譯器版本是:

      Apple clang version 4.0 (tags/Apple/clang-421.0.60) (based on LLVM 3.1svn)
      Target: x86_64-apple-darwin12.5.0
      Thread model: posix

 

      轉成中間文件的命令是:clang -rewrite-objc 源文件

3. 例子1

 

[objc] view plaincopy在CODE上查看代碼片派生到個人代碼片
  1. #include <stdio.h>  
  2.   
  3. int main(int argc, const charchar * argv[])  
  4. {  
  5.     int val = 2;  
  6.     int val1 = 5;  
  7.     void (^blk)(void) = ^{printf("in Block():val=%d\n", val);};  
  8.       
  9.     val = 4;  
  10.     blk();  
  11.     printf("in main(): val=%d, val1=%d\n", val, val1);  
  12.     return 0;  
  13. }  

      代碼的運行結果是:

      in Block():val=2

      in main():val=4, val1=5

      這裏咱們能夠看到Block對於自動變量的「快照」功能。因爲轉成中間文件以後發現長了不少,變成了100多行(不一樣的clang版本轉換出來的文件還不一樣,不過實現部分代碼仍是同樣的),下面的代碼是相關部分的節選,主要說明蘋果是如何實現的。

 

[objc] view plaincopy在CODE上查看代碼片派生到個人代碼片
  1. struct __block_impl {  
  2.   voidvoid *isa;  
  3.   int Flags;  
  4.   int Reserved;  
  5.   voidvoid *FuncPtr;  
  6. };  

[objc] view plaincopy在CODE上查看代碼片派生到個人代碼片
  1. #include <stdio.h>  
  2.   
  3. int main(int, const charchar **);  
  4.   
  5. struct __main_block_impl_0 {  
  6.   struct __block_impl impl;  
  7.   struct __main_block_desc_0* Desc;  
  8.   int val;  
  9.   __main_block_impl_0(voidvoid *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_val) {  
  10.     impl.isa = &_NSConcreteStackBlock;  
  11.     impl.Flags = flags;  
  12.     impl.FuncPtr = fp;  
  13.     Desc = desc;  
  14.   }  
  15. };  
  16. static void __main_block_func_0(struct __main_block_impl_0 *__cself) {  
  17.   int val = __cself->val; // bound by copy  
  18. printf("in Block():val=%d\n", val);}  
  19.   
  20. static struct __main_block_desc_0 {  
  21.   unsigned long reserved;  
  22.   unsigned long Block_size;  
  23. } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};  
  24. int main(int argc, const charchar * argv[])  
  25. {  
  26.     int val = 2;  
  27.     int val1 = 5;  
  28.     void (*blk)(void) = (void (*)(void))&__main_block_impl_0((voidvoid *)__main_block_func_0, &__main_block_desc_0_DATA, val);  
  29.       
  30.     val = 4;  
  31.     ((void (*)(struct __block_impl *))((struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk);  
  32.     printf("in main(): val=%d, val1=%d\n", val, val1);  
  33.     return 0;  
  34. }  

      中間文件確實看起來複雜了很多,不過仍是有脈絡可循。

      看main函數的內容,裏面有個函數指針blk,這個就是指向Block的指針,因此難怪Block變量的聲明和函數指針如此相像(就是把*換成^),編譯器轉換後就是同一個東西啊。

      咱們看blk這個函數指針,就是__main_block_impl_0這個結構體變量的指針,這個結構體變量此時已經存在,而後用__main_block_func_0等幾個變量賦初值。咱們能夠看到__main_block_impl_0這個struct中有個val這個項,而且在這裏也賦值了,這就是給變量照的「快照」,因爲這個變量在這裏被記錄了,因此不管外面的val變量如何變化,Block運行時使用的值就始終是「快照」的值了。同時咱們也注意到__main_block_impl_0這個struct中沒有val1這個項,因此說明若是Block中不用到的自動變量是不會自動加入到結構體中的。

      Block的運行就是運行__main_block_impl_0這個struct中的FuncPtr這個指針,這個在前面初始化的時候已經被賦值成__main_block_func_0了,因此這裏也就是運行這個函數,並把本身的指針傳入。這裏咱們的實現很是簡單,就是一句printf語句。

4.例子2

 

[objc] view plaincopy在CODE上查看代碼片派生到個人代碼片
  1. #include <stdio.h>  
  2.   
  3. int main(int argc, const charchar * argv[])  
  4. {  
  5.     int __block val = 2;  
  6.     int val1 = 5;  
  7.     void (^blk)(void) = ^{printf("in Block():val=%d\n", ++val);};  
  8.       
  9.     blk();  
  10.     printf("in main(): val=%d, val1=%d\n", val, val1);  
  11.     return 0;  
  12. }  

      這個例子把自動變量val聲明成了__block變量,這樣語法上Block不是對val進行「快照」,而是會直接使用val變量,同時在Block內部val可讀可寫,再也不是隻讀的了。

 

      運行結果以下:

      in Block():val=3
      in main(): val=3, val1=5

      一樣展開成中間文件來看蘋果的實現。

 

[objc] view plaincopy在CODE上查看代碼片派生到個人代碼片
  1. struct __block_impl {  
  2.   voidvoid *isa;  
  3.   int Flags;  
  4.   int Reserved;  
  5.   voidvoid *FuncPtr;  
  6. };  

[objc] view plaincopy在CODE上查看代碼片派生到個人代碼片
  1. #include <stdio.h>  
  2.   
  3. int main(int, const charchar **);  
  4. struct __Block_byref_val_0 {  
  5.   voidvoid *__isa;  
  6. __Block_byref_val_0 *__forwarding;  
  7.  int __flags;  
  8.  int __size;  
  9.  int val;  
  10. };  
  11.   
  12. struct __main_block_impl_0 {  
  13.   struct __block_impl impl;  
  14.   struct __main_block_desc_0* Desc;  
  15.   __Block_byref_val_0 *val; // by ref  
  16.   __main_block_impl_0(voidvoid *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {  
  17.     impl.isa = &_NSConcreteStackBlock;  
  18.     impl.Flags = flags;  
  19.     impl.FuncPtr = fp;  
  20.     Desc = desc;  
  21.   }  
  22. };  
  23. static void __main_block_func_0(struct __main_block_impl_0 *__cself) {  
  24.   __Block_byref_val_0 *val = __cself->val; // bound by ref  
  25. printf("in Block():val=%d\n", ++(val->__forwarding->val));}  
  26. static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}  
  27.   
  28. static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}  
  29.   
  30. static struct __main_block_desc_0 {  
  31.   unsigned long reserved;  
  32.   unsigned long Block_size;  
  33.   void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);  
  34.   void (*dispose)(struct __main_block_impl_0*);  
  35. } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};  
  36. int main(int argc, const charchar * argv[])  
  37. {  
  38.     __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 2};  
  39.     int val1 = 5;  
  40.     void (*blk)(void) = (void (*)(void))&__main_block_impl_0((voidvoid *)__main_block_func_0, &__main_block_desc_0_DATA, (struct __Block_byref_val_0 *)&val, 570425344);  
  41.       
  42.     ((void (*)(struct __block_impl *))((struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk);  
  43.     printf("in main(): val=%d, val1=%d\n", (val.__forwarding->val), val1);  
  44.     return 0;  
  45. }  

      中間文件又變長了一些,除去咱們已經瞭解的部分,咱們能夠看到自動變量val再也不是直接加入到__main_block_impl_0裏面,而是又變成了一個__Block_byref_val_0的struct結構體的指針。

      main函數裏面對於val的賦值已經變成了對這樣一個數據結構的賦值,第一句上就把2賦給了__Block_byref_val_0裏面的val項,而後在blk這個指針初始化的時候,把__Block_byref_val_0的結構體變量指針傳入__main_block_impl_0。此後全部對於自動變量val的操做都變成對val.__forwarding->val的操做。這樣就解決了Block內外變量同時變化的問題(在操做同一塊內存)。

      這裏還看見__Block_byref_val_0裏面有個__forwarding項,這個項是指向自身的一根指針。在blk指針初始化的時候咱們把這個指針的值傳入了__main_block_impl_0。

      在__main_block_desc_0裏面,多出了兩個函數指針,分別用於copy和dispose,這兩個函數這裏也是自動生成的。

5.總結

      綜合前面的例子來看,Block的實現仍是藉助了C語言的函數指針來實現了,對於普通的自動變量,在Block聲明時會快照內容存儲;對於__block變量,則是生成一個數據結構來存儲,而後取代全部訪問這個變量的地方。

      事實上,由於Block運行時徹底可能自動變量的生命週期已經結束,因此Block對於內存的管理是很複雜的,會把內容從棧上copy到堆上(這也是copy和dispose函數的做用)。因此Block雖然威力巨大,但使用時也須要遵循必定的規則。

 

 

 

 

iOS中Block介紹(二)內存管理與其餘特性

 

咱們在前一章介紹了block的用法,而正確使用block必需要求正確理解block的內存管理問題。這一章,咱們只陳述結果而不追尋緣由,咱們將在下一章深刻其緣由。

AD:2014WOT全球軟件技術峯會北京站 課程視頻發佈

1、block放在哪裏

咱們針對不一樣狀況來討論block的存放位置:

1.棧和堆

如下狀況中的block位於堆中:

  1. void foo()  
  2. {  
  3.     __block int i = 1024;  
  4.     int j = 1;  
  5.     void (^blk)(void);  
  6.     void (^blkInHeap)(void);  
  7.     blk = ^{ printf("%d, %d\n", i, j);};//blk在棧裏  
  8.     blkInHeap = Block_copy(blk);//blkInHeap在堆裏  
  9. }  
  10.    
  11. - (void)fooBar  
  12. {  
  13.     _oi = 1;  
  14.     OBJ1* oj = self;  
  15.     void (^oblk)(void) = ^{ printf("%d\n", oj.oi);};  
  16.     void (^oblkInHeap)(void) = [oblk copy];//oblkInHeap在堆中  

2.全局區

如下狀況中的block位於全局區:

  1. static int(^maxIntBlock)(int, int) = ^(int a, int b){return a>b?a:b;};  
  2. - (void)fooBar  
  3. {  
  4.      int(^maxIntBlockCopied)(int, int) =[maxIntBlock copy];  
  5. }  
  6. void foo()  
  7. {  
  8.      int(^maxIntBlockCopied)(int, int) = Block_copy(maxIntBlock);  

須要注意的是,這裏複製事後的block依舊位於全局區,實際上,複製操做是直接返回了原block對象。

2、block引用的變量在哪裏

 1.全局區

全局區的變量存儲位置與block無關:

  1. static int gVar = 0;  
  2. //__block static int gMVar = 1;  
  3. void foo()  
  4. {  
  5.     static int stackVar = 0;  
  6. //    __block static int stackMVar = 0;  

注意:static變量是不容許添加__block標記的

2.堆棧 

此時,你可能會問,當函數foo返回後,棧上的j已經回收,那麼blkInHeap怎麼能繼續使用它?這是由於沒有__block標記的變量,會被當作實參傳入block的底層實現函數中,當block中的代碼被執行時,j已經不是原來的j了,所謂物是人非就是這樣吧~

另外,若是使用到變量j的全部block都沒有被複制至heap,那麼這個變量j也不會被複制至heap。

所以,即便將j++這一句放到blk()這句以前,這段代碼執行後,控制檯打印結果也是:1024, 1。而不是1024, 2

3、其餘特性

1.複製的行爲

對block調用複製,有如下幾種狀況:

1.對全局區的block調用copy,會返回原指針,而且這期間不處理任何東西(至少目前的內部實現是這樣);

2.對棧上的block調用copy,每次會返回新複製到堆上的block的指針,同時,全部__block變量都會被複制至堆一份(屢次拷貝,只會生成一份)。

3.對已經位於heap上的block,再次調用copy,只會增長block的引用計數。

爲何咱們不討論retian的行爲?緣由是並無Block_retain()這樣的函數,並且objc裏面的retain消息發送給block對象後,其內部實現是什麼都不作。

2.objc類中的block複製

objc類實例方法中的block若是被複制至heap,那麼當前實例會被增長引用計數,當這個block被釋放時,此實例會被減小引用計數。

但若是這個block沒有使用當前實例的任何成員,那麼當前實例不會被增長引用計數。這也是很天然的道理,我既然沒有用到這個instance的任何東西,那麼我幹嗎要retian它?

咱們要注意的一點是,我看到網上有不少人說block引發了實例與block之間的循環引用(retain-cycle),而且給出解決方案:不直接使用self而先將self賦值給一個臨時變量,而後再使用這個臨時變量。

可是,你們注意,咱們必定要爲這個臨時變量增長__block標記(多謝第三篇文章回帖網友的提醒)。

這一章咱們以結果導向的方式來講明瞭各類狀況下,block的內存問題,下一章,我將剖析運行時庫的源碼,從根源闡述block的行爲。也就是過程導向的方式了。

 

 

 //Block

    // '^'脫字符

    

    //函數指針

    int (*p)(int, int) = sum;

    

    //調用函數

    sum(3, 4);

    p(2 ,4);

    

    //聲明一個Block變量

    /*1*///int:返回值   ,後面兩個int:參數類型

    int (^block1) (int, int) = nil;

    

    //block 變量存儲的值,是block的實現部分

    //實現部分返回值類型常省略

    //參數變量名不能省略

    //以';'結尾

    block1 = ^(int a, int b){

        return a + b;

    };

    

    //使用block1

    int sum = block1(2, 4);

    NSLog(@"%d",sum);

    

    

    /*2*//////////////////////////////////

    void (^block2)() = ^(){

        NSLog(@"無參數 無返回值的  block2(); ");

    };

    

    block2();//調用

    

    

    //3,求兩個數最大值 block

    int (^maxValue) (int x,int y) = ^(int a,int b){

        return a > b ? a : b;

    };

    

    int max = maxValue(2,3);//調用

    NSLog(@"最大值:%d",max);

 

    

    //4, 將字符串轉成數字的block

    int (^numberFromString)(NSString *) = ^(NSString *str){

        return [str intValue];

    };

    

    NSLog(@"%d",numberFromString(@"123"));//回調了 ^numberFromString 這個block

    

    

    //5, 給可變數組排序的block

    

    //5,1

    NSComparator sortBlock = ^(id string1, id string2)

    {

        return [string1 compare:string2];

    };

    

    NSMutableArray *stringArray = [NSMutableArray arrayWithObjects:@"a", @"c", @"b", @"d",nil];

    

 

    [stringArray sortedArrayUsingComparator:sortBlock];

    

    NSLog(@"sortArray:%@", stringArray);

 

    

    //5,2

    [stringArray sortUsingComparator:^NSComparisonResult(id obj1,id obj2){

        if ([obj1 intValue] > [obj2 intValue]) {

            return NSOrderedDescending;

        }

        return NSOrderedSame;

    }];

    

    NSLog(@"--%@",stringArray);

    

    

    //6, 取別名

    typedef int (^SumBlock) (int,int);

    

    SumBlock sumBlock1 = ^(int a,int b){

        return a + b;

    };

    

    NSLog(@"%d",sumBlock1(7,8));

    

    

    //7, 求階乘 的block

    int (^jiecheng)(int) = ^(int n){

        int m = 1;

        for (int i = 1; i <= n; i++) {

            m *= i;

        }

        return m;

    };

    

    NSLog(@"階乘:%d",jiecheng(3));

    

    //8, block內部能夠訪問外部局部變量,可是不能修改,若要修改需加__block 修飾

    __block int count = 2;

    

    void (^sayHi)() = ^(){

        count = 3;

        NSLog(@"count 被修改爲=%d",count);

    };

  

    sayHi();

    

    //9,

相關文章
相關標籤/搜索