Block在iOS開發中極常用,但許多開發者對其捕獲變量行為模糊不清。今天我們扒開Block真面目,從編譯器視角徹底講清四種變量捕獲場景。
判斷Block能否跟蹤變量變化,關鍵看變量生命週期是否超出Block定義作用域。只需記住:「即將銷毀的變量按值捕獲,長期存活的變量按引用捕獲」,再加上__block特殊修飾。
void test() {
int age = 10;
void (^block)() = ^{
NSLog(@"----age=%d", age); // 捕獲定義時的值 10
};
age = 20;
block(); // 輸出 10
}
編譯器視角等價代碼:
void test2() {
int age = 10;
void (^block)() = ^{
NSLog(@"----age=%d", 10); // 直接內嵌常量
};
age = 20;
block();
}
原理:局部變量棧上分配,作用域結束即銷毀。Block定義時拷貝當前值(值捕獲),後續修改無關。這很安全,因為變量隨作用域消亡,捕獲快照避免懸空引用。
__block修飾變量 —— 引用捕獲(可讀寫)void test3() {
__block int age = 10;
void (^block)() = ^{
NSLog(@"----age=%d", age); // 能跟蹤最新值
};
age = 20;
block(); // 輸出 20
}
原理:__block將變量「包裝」到堆上(或特殊結構體),Block持有指向該包裝體的指針。變量生命週期延長,Block內可讀寫最新值,無論局部/全局。
static局部變量 —— 引用捕獲(全局存活)void test4() {
static int age = 10;
void (^block)() = ^{
NSLog(@"----age=%d", age); // 動態訪問
};
age = 20;
block(); // 輸出 20
}
原理:static變量存儲在全局數據區,程序運行期間永不銷毀。Block捕獲其地址,每次調用動態讀取最新值。
int num = 10;
void test5() {
void (^block)() = ^{
NSLog(@"----num=%d", num);
};
num = 20;
block(); // 輸出 20
}
原理:全局變量同static,存儲於全局區,生命週期覆蓋整個程序。Block捕獲地址,實時訪問。
| 變量類型 | 存儲區 | 生命週期 | Block捕獲方式 | 能否跟蹤修改 |
|---|---|---|---|---|
| 普通局部 | 棧 | 作用域內 | 值捕獲(拷貝) | 否 |
__block局部 | 堆/包裝體 | 延長至Block釋放 | 引用捕獲(指針) | 是 |
static局部 | 全局數據區 | 程序全周期 | 引用捕獲(地址) | 是 |
| 全局變量 | 全局數據區 | 程序全周期 | 引用捕獲(地址) | 是 |
「只需判斷變量是否馬上銷毀:即將銷毀→值捕獲;長期存活→引用捕獲。特別注意
__block強制延長壽命。」
掌握這四種場景,Block變量捕獲不再是迷,面試與實戰都能游刃有餘!