How Do I Declare A Block in Objective-C?網絡
阮一峯的一句話解釋簡潔明瞭:閉包就是可以讀取其它函數內部變量的函數多線程
詳情:http://blog.csdn.net/jasonblog/article/details/7756763閉包
block的幾種適用場合:app
- 任務完成時回調處理
- 消息監聽回調處理
- 錯誤回調處理
- 枚舉回調
- 視圖動畫、變換
- 排序
local variable
1
|
returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...}; |
property
1
|
@property (nonatomic, copy) returnType (^blockName)(parameterTypes); |
method parameter
1
|
- (void)someMethodThatTakesABlock:(returnType (^)(parameterTypes))blockName; |
method call
: 1
|
[someObject someMethodThatTakesABlock:^returnType (parameters) {...}]; |
typedef
1
2 |
typedef returnType (^TypeName)(parameterTypes); TypeName blockName = ^returnType(parameters) {...}; |
數據類型
任何須要的時候
被執行匿名函數
,能夠做爲其餘函數的參數或者返回值。 1
2 |
//Block 是對 C 語言的一個拓展 //快速建立 Block 用 inlineBlock |
1
2 3 |
返回值類型 (^block變量名)(形參列表) = ^(形參列表) { }; |
1
2 3 4 5 6 |
void (^block名)() = ^{代碼塊;} 例如: void (^myBlock)() = ^{ NSLog(@"Damonwong"); }; |
1
2 3 4 5 6 7 |
void (^block名稱)(參數列表) = ^ (參數列表) { // 代碼實現; } 例如: void (^myBlock)(int) = ^(int num){ NSLog(@"num = %i", num); }; |
1
2 3 4 5 6 7 |
返回類型 (^block名稱)(參數列表) = ^ 返回類型 (參數列表) { // 代碼實現; } 例如: int (^myBlock)(int, int) = ^(int num1, int num2){ return num1 + num2; }; |
1
|
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" |
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 用起來特別方便,可是要特別注意循環應用的問題。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,從而破除循環。
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()這個宏。
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 直接引用外部強指針變量所形成的循環引用。