Block 再学习 !
如何優雅的使用 Block?
How Do I Declare A Block in Objective-C?
阮一峰的一句話解釋簡潔明了:閉包就是能夠讀取其它函數內部變量的函數
詳情:http://blog.csdn.net/jasonblog/article/details/7756763
block的幾種適用場合:
- 任務完成時回調處理
- 消息監聽回調處理
- 錯誤回調處理
- 枚舉回調
- 視圖動畫、變換
- 排序
作為基本變量 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最簡單形式(無參無返回值)
| 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 本質是不能修改變量存儲的值,但是消息分發依舊有效。
?
?
?
Block 的循環引用問題
雖然 Block 用起來特別方便,但是要特別注意循環應用的問題。
| 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,就造成了傳說中的循環引用。
為了避免這種情況發生,可以在變量聲明時用?__weak修飾符修飾變量 self,讓 block 不強引用 self,從而破除循環。
| 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; } |
黑科技,防止循環引用
| 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 中,并增加引用計數。 所以block2和block3的類型都是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()這個宏。
?
?
一、根據需求提出問題
- 請耐心把這篇文章看完,你對 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 對象。
二、深入探究原理
- 這里將會對每行代碼逐步進行說明 1、開辟一段控件存儲 person 類對象內容,創建 person 強指針。 MitPerson*person = [[MitPerson alloc]init]; 2、創建一個弱指針 weakPerson 指向person對象內容 __weak MitPerson * weakPerson = person; person.mitBlock = ^{ 3、在 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];}); }; 4、執行 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 的概念:
- 1、默認情況下,block 是放在棧里面的
- 2、一旦blcok進行了copy操作,block的內存就會被放在堆里面
- 3、堆立面的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 -
?
?
- 總結:person.mitBlock 中創建 strongPerson 是為了能夠使 GCD Block 保存 person 對象,創建 strongPerson 時候使用 weakPerson 是為了避免 mitBlock 直接引用外部強指針變量所造成的循環引用。
總結
以上是生活随笔為你收集整理的Block 再学习 !的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Log4j 2使用教程转
- 下一篇: ajax传递复杂参数