iOS Block Tips

Block 簡介

Mac OS X系統10.4及其iOS 4.0後引入了閉包的概念,這項語言特性是做爲擴展而加入GCC編譯器的。在Foundation框架中大量使用了Block。 塊就是一個實現某個功能的函數閉包,這個函數閉包能夠帶有參數,也能夠沒有參數,能夠有返回值也能夠沒有返回值者,用符號'^'來表示。塊在聲明的範圍內,能夠調用塊外部的全局變量和局部變量。安全

void (^someBlock) () = ^{
    //A simple block
    //Implementation: some code
    //無返回值,無參數
}

void (^block) (int a, bool b) = ^(int a, bool b) {
   //some code
   //無返回值,帶參數
}

int (^block2) (int a, bool b) = ^(int a, bool b) {
   //some code
   //帶參數帶返回值
   return integerValue;
}
複製代碼

須要主意的是,block內部不能改變外部變量,想要在Block中改變變量的值,那麼咱們只須要在變量聲明的時候加上__Block修飾符。數據結構

__block int a = 0;
void (^block)() = ^{
    a = 33;
};
複製代碼

Block 的內部結構

每一個Object-c變量都佔據着某個內存區域,block自己也是一個對象,在存放block對象的內存區域中,首個變量是指向class的指針isa,其他內存裏包含着對象的其餘全部信息。閉包

block內存佈局.png

  • isa 指針,全部對象都有該指針,用於實現對象相關的功能。框架

  • flags,用於按 bit 位表示一些 block 的附加信息,本文後面介紹 block copy 的實現代碼能夠看到對該變量的使用。ide

  • reserved,保留block函數代碼內的變量。函數

  • invoke,函數指針,指向具體的 block 實現的函數調用地址。在內存佈局中最重要的就是invoke函數指針,指向block的實現代碼佈局

  • descriptor,是指向結構體的指針,每一個塊裏都包含此結構體。block將所捕獲的變量指針拷貝到descriptor變量後。表示 block 的附加描述信息,主要是 size 大小,以及 copy 和 dispose 函數的指針。this

  • variables,capture 過來的變量,block 可以訪問它外部的局部變量,就是由於將這些變量(或變 量的地址)複製到告終構體中。spa

對於 block 外的變量引用,block 默認是將其複製到其數據結構中來實現訪問的。對於用 __block 修飾的外部變量引用,block是複製其引用地址來實現訪問指針

全局block/堆block/棧block

定義block的時候,其所佔的內存區域是分配在棧中的。 在 Objective-C 語言中,一共有 3 種類型的 block:

  • _NSConcreteGlobalBlock 全局的靜態 block,不會訪問任何外部變量
  • _NSConcreteStackBlock 保存在棧中的 block,當函數返回時會被銷燬
  • _NSConcreteMallocBlock 保存在堆中的 block,當引用計數爲 0 時會被銷燬

下面的這段代碼在執行的時候就很危險:

void (^block)();
  int a = 3;
  if (a > 0) {
     block = ^ { NSLog(@"Block A") };
  } else {
     block = ^ { NSLog(@"Block B") };
  }
複製代碼

在定義if else 語句中的兩個block都分配在棧內存區域,編譯器會給每一個block分配好內存,然而等離開相應的範圍後,編譯器有可能會把分配給塊的內存覆蓋掉。因而這兩個塊只能保障在對應的if else語句範圍內有效,這樣的代碼運行起來就會出現問題。 爲解決此問題,能夠給block對象發送copy消息以拷貝到堆空間裏。一旦複製到堆上,block就成了帶引用計數器的對象了。後續的複製操做都不會真的執行復制,只是遞增塊對象的應用計數器。 如下代碼就是安全的:

void (^block)();
  int a = 3;
  if (a > 0) {
     block = [^{ NSLog(@"Block A") } copy];
  } else {
     block = [^{ NSLog(@"Block B") } copy];
  }
複製代碼

與全局變量相似,全局塊所使用的內存區域,在編譯期就已經徹底肯定了,全局塊能夠聲明在全局內存裏。下面就是一個簡單的全局塊:

void (^block) () = ^ {
NSLog(@"this is a global block");
}
複製代碼

使用Block小技巧

Tip 1 爲常見block類型建立塊,聲明變量時,要把名稱放在類型中間:

typedef  int (^BLOCKSOME) (bool flag, int value);
BLOCKSOME block = ^(bool flag, int value) {
//some code
};
複製代碼

Tip 2

用塊引用及其所屬對象時,不要保留閉環,防止出現return cycle。如使用weakself來防止return cycle:

__weak  ViewController *wself = self;
複製代碼

定義一個wself變量並加上__weak修飾符,在Block代碼塊中,全部須要self的地方都用wself來替代。這樣就不會增長引用計數,因此Block持有self對象也就不會形成循環引用,從而形成內存泄漏。

參考文章: blog.devtang.com/2013/07/28/…

相關文章
相關標籤/搜索