簡單理解 Block
一、萬年不變老問題:什么是 Block
Block 是一段代碼塊,可以簡單的理解為帶有自動變量的 匿名函數 , 自動變量 可以理解為 局部變量 , 匿名函數 就是沒有名字的函數。 Block 可以像 函數 一樣,傳入 參數 ,得到 返回值 。
二、Block 的聲明與定義
-
Block 變量的聲明
int (^myBlock)(int);
上述代碼使用操作符 ^ 聲明了一個名為 myBlock 的變量。入參為 int 類型,返回值為 int 類型。
-
Block 的定義
myBlock = ^(int num) { return num * 2; };
^ 操作符表示 Block 語句的開始;
() 中為參數列表;
{} 中是實現的實現的實體;
于是我們就知道上面的代碼在說什么了:傳入參數 num ,返回 int 類型的對象,賦值給 myBlock 。
完整的分析可以看下圖
1.jpg
-
與函數的不同
- 沒有函數名;(匿名函數嘛~)
- 帶有操作符 ^ ;
三、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 的內存泄露有兩種辦法:
- 使用 __block;
- 使用 __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