Block 再學習 !

如何優雅的使用 Block?

How Do I Declare A Block in Objective-C?網絡

阮一峯的一句話解釋簡潔明瞭:閉包就是可以讀取其它函數內部變量的函數多線程

詳情:http://blog.csdn.net/jasonblog/article/details/7756763閉包

block的幾種適用場合:app

  • 任務完成時回調處理
  • 消息監聽回調處理
  • 錯誤回調處理
  • 枚舉回調
  • 視圖動畫、變換
  • 排序

做爲基本變量 As a local variable

1
returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...}; 

做爲對象屬性 As a property

1
@property (nonatomic, copy) returnType (^blockName)(parameterTypes); 

做爲方法參數 As a method parameter

1
- (void)someMethodThatTakesABlock:(returnType (^)(parameterTypes))blockName; 

做爲回調 As an argument to a method call:

1
[someObject someMethodThatTakesABlock:^returnType (parameters) {...}]; 

做爲類型別名 As atypedef

1
2 
typedef returnType (^TypeName)(parameterTypes); TypeName blockName = ^returnType(parameters) {...};

 

什麼是Block

  • Block是iOS中一種比較特殊的數據類型
  • 是一個能工做的代碼單元,能夠在任何須要的時候被執行
  • 本質是輕量級的匿名函數,能夠做爲其餘函數的參數或者返回值。
  • Block是蘋果官方特別推薦使用的數據類型, 應用場景比較普遍
    • 動畫
    • 多線程
    • 集合遍歷
    • 網絡請求回調
  • Block的做用
    • 用來保存某一段代碼, 能夠在恰當的時間再取出來調用
    • 功能相似於函數和方法
1
2 
//Block 是對 C 語言的一個拓展 //快速建立 Block 用 inlineBlock 

Block的格式

  • Block的定義格式
1
2 3 
返回值類型 (^block變量名)(形參列表) = ^(形參列表) {  }; 

Block

  • block最簡單形式(無參無返回值)
1
2 3 4 5 6 
void (^block)() = ^{代碼塊;}  例如: void (^myBlock)() = ^{  NSLog(@"Damonwong"); }; 
  • block中級進階形式(有參無返回值)
1
2 3 4 5 6 7 
void (^block名稱)(參數列表) = ^ (參數列表) { // 代碼實現; }  例如: void (^myBlock)(int) = ^(int num){  NSLog(@"num = %i", num); }; 
  • block高級進階形式(有參有返回值)
1
2 3 4 5 6 7 
返回類型 (^block名稱)(參數列表) = ^ 返回類型 (參數列表) { // 代碼實現; }  例如: int (^myBlock)(int, int) = ^(int num1, int num2){  return num1 + num2; }; 
  • 調用Block保存的代碼
1
block變量名(實參); 

 

Block 與 變量

觀察下面四段代碼的輸出值框架

  • Block 能夠讀取變量,可是默認狀況下不能修改變量的值
1
2 3 4 5 6 7 8 
int x = 123; void (^printXAndY)(int) = ^(int y) {  x = 100; //會報錯,  printf("%d %d\n", x, y); }; printXAndY(456); /*------------------*/ @"prints: 123 456" 
  • Block 能讀取變量的原理是 「copy」了一份變量的值。因此在 Block 定義以後修改變量的值,再調用 Block,值依舊是修改前的。換句話說,定義好 Block 以後,修改變量值對 Block 無效。
1
2 3 4 5 6 7 8 
int x = 123; void (^printXAndY)(int) = ^(int y) {  printf("%d %d\n", x, y); }; x = 456; // 修改 x 爲 456,block 依舊輸出 123 printXAndY(456); /*------------------*/ @"prints: 123 456" 
  • __blcok關鍵字的神奇功效。

首先,若是須要對block 外部定義的變量在 block 內修改,那麼須要對這個變量添加一個__block修飾。函數

1
2 3 4 5 6 7 8 
__block int x = 123; void (^printXAndY)(int) = ^(int y) {  x = 100; //不會報錯  printf("%d %d\n", x, y); }; printXAndY(456); /*------------------*/  @"prints: 100 456" 

若是須要在調用以前,變量的修改都會影響 block 內部對這個變量的使用,換句話說,block 對變量再也不是簡單的值複製,而是動態的"監聽"值的變化,而後在調用的時候讀取變量的值。須要對這個變量添加一個__block修飾。動畫

1
2 3 4 5 6 7 8 
__block int x = 123; void (^printXAndY)(int) = ^(int y) {  printf("%d %d\n", x, y); }; x = 456 //block 會「動態」識別外部變量的變化,輸出456 printXAndY(456); /*------------------*/ @"prints: 456 456" 
  • 注意區別變量指針所指向的變量
1
2 3 4 5 6 7 8 
int x = 123; NSMutableString *str = [NSMutableString stringWithFormat:@"Damon"]; void (^printStr)() = ^() {  [str appendString:@"wong"];  NSLog(@"%@",str); }; printStr(); // prints:Damonwong 

這裏的 [str appendString:@"wong"];不報錯是由於str 是指向@"Damon"的函數指針,[str appendString:@"wong"];並非修改 str 存儲的值,本質上是 str 向@"Damon"發送了一條appendString 消息,而後再更改@"Damon"@"Damonwong",而 str 存儲的指向@"Damonwong"對象的指針沒有發生變化。因此,block 本質是不能修改變量存儲的值,可是消息分發依舊有效。ui


 


 

 

Block 的循環引用問題

雖然 Block 用起來特別方便,可是要特別注意循環應用的問題。this

1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 
// ARC enabled /************** MyObject Class **************/ typedef void (^myBlock)(void); @interface MyObject : NSObject {  myBlock block; } @end @implementation MyObject - (id)init {  self = [super init];  block = ^{  NSLog(@"self = %@", self);  };  return self; } - (void)dealloc {  NSLog(@"dealloc"); } @end /************** main function **************/ int main() {  id myObject = [[MyObject alloc] init];  NSLog(@"%@", myObject);  return 0; } 

因爲 self 是 __strong 修飾,在 ARC 下,當編譯器自動將代碼中的 block 從棧拷貝到堆時,block 會強引用和持有 self,而self 剛好也強引用和持有了 block,就形成了傳說中的循環引用。atom

爲了不這種狀況發生,能夠在變量聲明時用 __weak修飾符修飾變量 self,讓 block 不強引用 self,從而破除循環。

Block 循環引用

1
2 3 4 5 6 7 8 9 
- (id)init {  self = [super init];  __weak typeof(self) weak_self = self;  block = ^{  NSLog(@"self = %@", weak_self);  };  return self; } 

避免 Block 循環引用

黑科技,防止循環引用

1
2 3 4 5 6 7 8 9 10 11 
- (id)init {  self = [super init];  __block typeof(self) temp = self;  block = ^{  NSLog(@"self = %@", temp);  temp = nil;  };  return self; } // 使用這個,必須調用一次 block 

Tips

  • 宏定義:#define Weak_Ref(obj) _weak typeof(obj) weak##obj = obj;

  • 注意self.name這類點語法,[self name]消息傳遞及 self

  • block 中使用 self不必定形成循環引用,但可能性極大

  • 重寫 dealloc 方法能夠很方便的知道是否存在循環引用

 

 

Block 在內存中的位置

因爲block也是NSObject,咱們能夠對其進行retain操做。不過在將block做爲回調函數傳遞給底層框架時,底層框架須要對其copy一份。比方說,若是將回調block做爲屬性,不能用retain,而要用copy。咱們一般會將block寫在棧中,而須要回調時,每每回調block已經不在棧中了,使用copy屬性能夠將block放到堆中。或者使用Block_copy()和Block_release()。

  • 根據Block在內存中的位置分爲三種類型
    • NSGlobalBlock:相似函數,位於text段,全局的靜態 block,不會訪問任何外部變量
    • NSStackBlock :保存在棧中的 block,當函數返回時會被銷燬
    • NSMallocBlock:保存在堆中的 block,當引用計數爲 0 時會被銷燬。

MRC 下的 Block

1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
 /*------------MRC-----------------*/  typedef long (^MyBlock)(int, int);   MyBlock block1 = ^ long (int a, int b) {  return a + b;  };  NSLog(@"block1 = %@", block1);  // block1 = <__NSGlobalBlock__: 0x47d0>   int base = 100;  MyBlock block2 = ^ long (int a, int b) {  return base + a + b;  };  NSLog(@"block2 = %@", block2);  // block2 = <__NSStackBlock__: 0xbfffddf8>   MyBlock block3 = [[block2 copy] autorelease];  NSLog(@"block3 = %@", block3);  // block3 = <__NSMallocBlock__: 0x902fda0> 

block1沒有使用任何外部變量,所以存儲在 代碼區,編譯器給其的類型爲NSGlobalBlock

block2使用到了局部變量,在定義(注意是定義,不是運行)block2時,局部變量base當前值被copy到棧上,做爲常量供Block使用。編譯器給其類型爲NSStackBlock

block3 通過拷貝,局部變量 base 的值被 copy 到堆中,編譯器給其類型爲NSMallocBlock 總結說來,block 的類型取決於內部使用的變量在哪。

ARC 下的 Block

1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 
/*------------ARC-----------------*/ typedef long (^MyBlock)(int, int); MyBlock block1 = ^ long (int a, int b) {  return a + b; }; NSLog(@"block1 = %@", block1); // block1 = <__NSGlobalBlock__: 0x100001080>  int base = 100; MyBlock block2 = ^ long (int a, int b) {  return base + a + b; }; NSLog(@"block2 = %@", block2); // block2 = <__NSMallocBlock__: 0x100203cf0>  __block int sum = 100; MyBlock block3 = ^ long (int a, int b) {  return sum + a + b; }; NSLog(@"block3 = %@", block3); // block3 = <__NSMallocBlock__: 0x100207100> 

由於 ARC 下,編譯器幫咱們管理內存,因此只要內部調用了外部變量,編譯器都會 copy 一份變量到heap 中,並增長引用計數。 因此block2block3的類型都是NSMallocBlock。其他和 MRC 同樣。

Tops:

如下狀況,block 會拷貝到堆:

  • 當 block 調用 copy 方法時,若是 block 在棧上,會被拷貝到堆上;

  • 當 block 做爲函數返回值返回時,編譯器自動將 block 做爲 _Block_copy 函數,效果等同於 block 直接調用 copy 方法;

  • 當 block 被賦值給 _strong id 類型的對象或 block 的成員變量時,編譯器自動將 block 做爲Block_copy 函數,效果等同於 block 直接調用 copy 方法;

  • 當 block 做爲參數被傳入方法名帶有 usingBlock 的 Cocoa Framework 方法或 GCD 的 API 時。這些方法會在內部對傳遞進來的 block 調用 copy 或 _Block_copy 進行拷貝;

 

Objective-C Blocks Quiz


Example A

1
2 3 4 5 6 
void exampleA() {  char a = 'A';  ^{  printf("%cn", a);  }(); } 
  • Always works

Explain

This always works. The stack for exampleA doesn’t go away until after the block has finished executing. So whether the block is allocated on the stack or the heap, it will be valid when it is executed.

函數exampleA不會消失,直到 block 運行結束,因此無論 block 在堆中仍是棧中,它均可以運行。


Example B

1
2 3 4 5 6 7 8 9 10 11 12 13 
void exampleB_addBlockToArray(NSMutableArray *array) {  char b = 'B';  [array addObject:^{  printf("%cn", b);  }]; }  void exampleB() {  NSMutableArray *array = [NSMutableArray array];  exampleB_addBlockToArray(array);  void (^block)() = [array objectAtIndex:0];  block(); } 
  • Only works in ARC

Explain

Without ARC, the block is an NSStackBlock allocated on the stack of exampleB_addBlockToArray. By the time it executes in exampleB, the the block is no longer valid, because that stack has been cleared.

With ARC, the block is properly allocated on the heap as an autoreleased NSMallocBlock to begin with.

在 MRC中,這裏的block 存在棧中,因此在執行exampleB函數的exampleB_addBlockToArray(array);以後,b 變量變得無效,因此[array objectAtIndex:0]不能成功。 在 ARC 中,這個 block 存在堆中,當運行到[array objectAtIndex:0],block 還沒被釋放,因此能夠運行。


Example C

1
2 3 4 5 6 7 8 9 10 11 12 
void exampleC_addBlockToArray(NSMutableArray *array) {  [array addObject:^{  printf("Cn");  }]; }  void exampleC() {  NSMutableArray *array = [NSMutableArray array];  exampleC_addBlockToArray(array);  void (^block)() = [array objectAtIndex:0];  block(); } 
  • Always works

Explain

Since the block doesn’t capture any variables in its closure, it doesn’t need any state set up at runtime. it gets compiled as an NSGlobalBlock. It’s neither on the stack nor the heap, but part of the code segment, like any C function. This works both with and without ARC.

由於這裏的 block 是全局的NSConcreteGlobalBlock,因此不論是 ARC 仍是 MRC 都是能夠用的。


Example D

1
2 3 4 5 6 7 8 9 10 11 12 
typedef void (^dBlock)();  dBlock exampleD_getBlock() {  char d = 'D';  return ^{  printf("%cn", d);  }; }  void exampleD() {  exampleD_getBlock()(); } 
  • Only works in ARC

Explain

This is similar to example B. Without ARC, the block would be created on the stack of exampleD_getBlock and then immediately become invalid when that function returns. However, in this case, the error is so obvious that the compiler will fail to compile, with the error error: returning block that lives on the local stack.

With ARC, the block is correctly placed on the heap as an autoreleased NSMallocBlock.

在 MRC 時,相似於example B,block 會被建立在棧中,因此當block 返回時,立刻失效。在這種狀況下,錯誤是顯而易見的,編譯器沒法編譯成功。返回一個錯誤:returning block that lives on the local stack。 在 ARC 中,當自動釋放池銷燬,block 才失效


Example E

1
2 3 4 5 6 7 8 9 10 11 12 13 14 
typedef void (^eBlock)();  eBlock exampleE_getBlock() {  char e = 'E';  void (^block)() = ^{  printf("%cn", e);  };  return block; }  void exampleE() {  eBlock block = exampleE_getBlock();  block(); } 
  • Only works in ARC

Explain

This is just like example D, except that the compiler doesn’t recognize it as an error, so this code compiles and crashes. Even worse, this particular example happens to work fine if you disable optimizations. So watch out for this working while testing and failing in production.

With ARC, the block is correctly placed on the heap as an autoreleased NSMallocBlock.

這題相似於example D。在 MRC 會形成崩潰。


Block_copy & copy

[block copy] 和 Block_copy(block)不等效。block 的賦值不是簡單的拷貝,因此要拷貝最好使用 Block_copy()這個宏。

 

 

1、根據需求提出問題

  • 請耐心把這篇文章看完,你對 Block 會有更深入的瞭解。
  • 這裏直接用一個需求來探究循環引用的問題:若是我想在Block中延時來運行某段代碼,這裏就會出現一個問題,看這段代碼:
    - (void)viewDidLoad { [super viewDidLoad]; MitPerson*person = [[MitPerson alloc]init]; __weak MitPerson * weakPerson = person; person.mitBlock = ^{ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [weakPerson test]; }); }; person.mitBlock(); }
    直接運行這段代碼會發現[weakPerson test];並無執行,打印一下會發現,weakPerson 已是 Nil 了,這是因爲當咱們的 viewDidLoad 方法運行結束,因爲是局部變量,不管是 MitPerson 和 weakPerson 都會被釋放掉,那麼這個時候在 Block 中就沒法拿到正真的 person 內容了。
  • 按以下方法修改代碼:
    - (void)viewDidLoad { [super viewDidLoad]; MitPerson*person = [[MitPerson alloc]init]; __weak MitPerson * weakPerson = person; person.mitBlock = ^{ __strong MitPerson * strongPerson = weakPerson; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [strongPerson test]; }); }; person.mitBlock(); }
    這樣當2秒事後,計時器依然可以拿到想要的 person 對象。

2、深刻探究原理

    • 這裏將會對每行代碼逐步進行說明
      一、開闢一段控件存儲 person 類對象內容,建立 person 強指針。 MitPerson*person = [[MitPerson alloc]init];
      二、建立一個弱指針 weakPerson 指向person對象內容 __weak MitPerson * weakPerson = person;
      person.mitBlock = ^{
      三、在 person 對象的 Block 內部建立一個強指針來指向 person 對象,爲了保證當計時器執行代碼的時候,person 對象沒有被系統銷燬因此咱們必須在系統內部進行一次強引用,並用 GCD 計時器引用 strongPerson,爲了保留 person 對象,在下面會對這裏更加詳細的說明。 __strong MitPerson * strongPerson = weakPerson; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [strongPerson test]; }); };
      四、執行 Block 代碼 person.mitBlock();
    • 下面將詳細分析一下下面這段代碼:
      person.mitBlock = ^{ __strong MitPerson * strongPerson = weakPerson; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [strongPerson test]; }); };
    • 首先須要明白一些關於 Block 的概念:
      • 一、默認狀況下,block 是放在棧裏面的
      • 二、一旦blcok進行了copy操做,block的內存就會被放在堆裏面
      • 三、堆立面的block(被copy過的block)有如下現象
        • 1> block內部若是經過外面聲明的強引用來使用,那麼block內部會自動產生一個強引用指向所使用的對象。
        • 2> block內部若是經過外面聲明的弱引用來使用,那麼block內部會自動產生一個弱引用指向所使用的對象。
    • 咱們進行這段代碼的目的:
      • 首先,咱們須要在 Block 塊中調用,person 對象的方法,既然是在 Block 塊中咱們就應該使用弱指針來引用外部變量,以此來避免循環引用。可是又會出現問題,什麼問題呢?就是當我計時器要執行方法的時候,發現對象已經被釋放了。
      • 接下來就是爲了不 person 對象在計時器執行的時候被釋放掉:那麼爲何 person 對象會被釋放掉呢?由於不管咱們的person強指針仍是 weakPerson 弱指針都是局部變量,當執行完ViewDidLoad 的時候,指針會被銷燬。對象只有被強指針引用的時候纔不會被銷燬,而咱們若是直接引用外部的強指針對象又會產生循環引用,這個時候咱們就用了一個巧妙的代碼來完成這個需求。
      • 首先在 person.mitBlock 引用外部 weakPerson,並在內部建立一個強指針去指向 person 對象,由於在內部聲明變量,Block 是不會強引用這個對象的,這也就在避免的 person.mitBlock 循環引用風險的同時,又建立出了一個強指針指向對象。
      • 以後再用 GCD 延時器 Block 來引用相對於它來講是外部的變量 strongPerson ,這時延時器 Block 會默認建立出來一個強引用來引用 person 對象,當 person.mitBlock 做用域結束以後 strongPerson 會跟着被銷燬,內存中就僅剩下了 延時器 Block 強引用着 person 對象,2秒以後觸發 test 方法,GCD Block 內部方法執行完畢以後,延時器和對象都被銷燬,這樣就完美實現了咱們的需求。
    • 最後再用一張圖來闡述各個指針、Block 與對象之間的關係
      黑色表明強引用,綠色表明弱引用
      • 總結:person.mitBlock 中建立 strongPerson 是爲了可以使 GCD Block 保存 person 對象,建立 strongPerson 時候使用 weakPerson 是爲了不 mitBlock 直接引用外部強指針變量所形成的循環引用。

        Block循環引用.png
相關文章
相關標籤/搜索