簡單理解 Block

HyeSimone 8年前發布 | 5K 次閱讀 iOS開發 移動開發

一、萬年不變老問題:什么是 Block

Block 是一段代碼塊,可以簡單的理解為帶有自動變量的 匿名函數 , 自動變量 可以理解為 局部變量 , 匿名函數 就是沒有名字的函數。 Block 可以像 函數 一樣,傳入 參數 ,得到 返回值 。

二、Block 的聲明與定義

  1. Block 變量的聲明

    int (^myBlock)(int);

    上述代碼使用操作符 ^ 聲明了一個名為 myBlock 的變量。入參為 int 類型,返回值為 int 類型。

  2. Block 的定義

    myBlock = ^(int num) {
         return num * 2;
     };

    ^ 操作符表示 Block 語句的開始;

    () 中為參數列表;

    {} 中是實現的實現的實體;

    于是我們就知道上面的代碼在說什么了:傳入參數 num ,返回 int 類型的對象,賦值給 myBlock 。

    完整的分析可以看下圖

    1.jpg

  3. 與函數的不同

    1. 沒有函數名;(匿名函數嘛~)
    2. 帶有操作符 ^ ;
    </li> </ol>

    三、Block 的使用

    1. Block 的調用

    好了,我們已經知道怎么聲明及定義 Block ,那么怎么使用呢?超簡單,上面說了 Block 與 函數很像,想想我們是怎么使用函數的?

    NSLog(@"================%d", myBlock(3));
    //結果為6

    2. Block 作為函數參數

    添加 typedef 關鍵字,聲明一個 Block 類型變量。添加關鍵字的目的是,可以直接使用名稱 nameBlock 。

    typedef void(^nameBlock)(NSString *name);

    將 nameBlock 作為入參,實現一個函數。

    - (void)nameFunction:(nameBlock)nameBlock {
        nameBlock(@"小井");
    }

    對于函數的使用,直接調用時:

    [self nameFunction:^(NSString *name) {
        if (![name isEqualToString:@""]) {
            NSLog(@"My name is %@", name);
        }
    }];
    // 結果為 My name is 小井

    四、Block 中變量的修改

    聲明一個變量 temp , 聲明一個 testBlock ,在 testBlock 的實現體中打印出變量 temp 的值。

    int temp = 0;
    void (^testBlock)() = ^{
        NSLog(@"temp = %d", temp);

    }; temp = 1; testBlock(); //結果 temp = 0</code></pre>

    從最后的打印結果可以看出,盡管在調用 testBlock 之前,對變量 temp 重新賦值為 1, 打印結果仍為 0。于是我們知道了:

    Block 在訪問外部變量時,會拷貝一份到自己的數據存儲中。

    為了證明我們的觀點,分別在 testBlock 實現體內部和實現體外部打印下地址。

    int temp = 0;  
    void (^testBlock)() = ^{
        NSLog(@"temp = %d", temp);
        NSLog(@"內部 temp is %ld", &temp);
    };
    temp = 1;
    NSLog(@"外部 temp is %ld", &temp);
    testBlock();
     //結果 
     // 外部 temp is 140734745021036
     // temp = 0
     // 內部 temp is 106102872376448

    果然,地址不一樣了。

    下面我們嘗試在 testBlock 中修改變量 temp 的值,在 testBlock 的實現實體中,添加如下一句代碼。

    temp = 2;

    會發現,編譯器報錯了。這是因為 Block 拷貝的變量值是 const 的,即,在 Block 內部不能隨意修改。但是當我確實有這樣的需求,希望在 Block 內部修改外部變量時,怎么辦呢?當當當~~~,只需要在外部變量的聲明之前加上 __block 關鍵字,就可以愉快的在 Block 內部修改變量的值了,我們試試。

    __block int temp = 0;

    void (^testBlock)() = ^{ temp = 2; NSLog(@"temp = %d", temp); NSLog(@"內部 temp is %ld", &temp); }; temp = 1; NSLog(@"外部 temp is %ld", &temp); testBlock(); // 結果 // 外部 temp is 106102872333208 // temp = 2 // 內部 temp is 106102872333208</code></pre>

    我們可以發現,在 testBlock 內部成功的修改了變量 temp 的值,并且,跟之前不一樣的是,這次的變量地址也相同的。因為加入了 _block 修飾符后, Block 不再拷貝原變量,而是拷貝原變量的引用地址,即這次是把指針拷貝了過來,指針指向原變量地址

    2.jpg

    五、Block 深坑之循環引用

    block 的使用不當會造成循環引用,內存泄露。

    3.jpg

    typedef void (^block_t)(void);

    @interface TestObject : NSObject { blockt block; } @end

    @implementation TestObject

    • (id)init { self = [super init]; block_ = ^(void) {

          NSLog(@"self = %@", self);
      

      }; return self; }</code></pre>

      上面的代碼編譯器會顯示 warning

      Capturing ‘self’ strongly in this block is likely to lead to a retain cycle;

      block_ 是 self 的成員變量, self 持有 Block 的強引用。在 init 初始化方法中, Block 的實現體中,使用了 id 類型的 self , 賦值給了成員變量 block_ ,Block 語法自動由棧拷貝到堆,Block 持有了 self ,于是造成了循環引用。當 main 函數結束時,由于循環引用的存在,堆上的對象不能釋放,造成了內存泄露。如下圖:

      4.jpg

      解決 Block 的內存泄露有兩種辦法:

      1. 使用 __block;
      2. 使用 __weak;

      先說使用 __weak :聲明 __weak 屬性的臨時變量 temp , 并將 self 賦值給臨時變量。

      - (id)init {
        self = [super init];
        id __weak temp = self;
        block_ = ^(void) {
            NSLog(@"self = %@", temp);
        };
        return self;
      }

      5.jpg

      再說使用 __block 方法:

      - (id)init {
          self = [super init];
            __block id temp = self;
      
        block_ = ^(void) {
            NSLog(@"self = %@", temp);
            temp = nil;
        };
        return self;
      }

      可以分析出:

      self 持有 Block ;

      Block 持有 __block 變量;

      __block 變量持有 self ;

      6.jpg

      從圖上可以看出,還是會存在循環引用的。此時只需要顯示的調用下 block_() 就能解決問題,因為在 Block 的執行體中, temp 變量被賦值為 nil , 對 self 的強引用失效,故解除了循環引用。

      7.jpg

       

       

      來自:http://www.jianshu.com/p/5371a9c27e8d

       

 本文由用戶 HyeSimone 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!