Block 是 Objective-C 中極具威力的特性,許多開發者卻對其捕獲變量的機制感到困惑。本文將剖析 Block 對局部變量、__block 修飾變量、static 變量以及全域變量的四種不同捕獲行為,幫你徹底搞懂 Block 內部的變量世界。
void test() {
int age = 10;
void (^block)() = ^{
NSLog(@"----age=%d", age); // 輸出 10
};
age = 20;
block();
}
編譯器實際上將 Block 內的 age 直接替換為常數 10。因為局部變量存放在棧上,Block 定義時會將其值「拷貝」一份,後續外部修改 age 與 Block 內部無關。這也是為什麼 Block 捕獲局部變量是安全的——即使變量離開作用域銷毀,Block 仍持有複製的值。
__block 修飾變量 —— 捕獲的是「變量本身」void test3() {
__block int age = 10;
void (^block)() = ^{
NSLog(@"----age=%d", age); // 輸出 20
};
age = 20;
block();
}
__block 會將變量包裝在一個結構體中,存放於堆上(或透過 runtime 管理),Block 內部持有指向該結構體的指標。因此 Block 內外共享同一塊記憶體,外部修改值,Block 內部立即可見。這讓 Block 能夠「寫回」外部變量,是實現雙向數據流的關鍵。
static 靜態變量 —— 捕獲的是「記憶體地址」void test4() {
static int age = 10;
void (^block)() = ^{
NSLog(@"----age=%d", age); // 輸出 20
};
age = 20;
block();
}
靜態變量存放在全域/靜態區,生命週期贯穿整個程式運行。Block 直接捕獲其記憶體地址,故能即時讀取最新值,行為類似全域變量。
int num = 10;
void test5() {
void (^block)() = ^{
NSLog(@"----num=%d", num); // 輸出 20
};
num = 20;
block();
}
全域變量同樣位於全域區,Block 捕獲其地址,實現動態訪問。
判斷 Block 能否追蹤變量變化,只需問一句:「這個變量會不會馬上銷毀?」
__block 修飾 → 強制延長生命週期 → Block 捕獲引用(可讀寫)static/全域變量 → 永不銷毀 → Block 捕獲地址(動態讀寫)若需在 Block 內修改外部局部變量,務必加上 __block;若僅需讀取且不希望受外部後續修改影響,直接使用普通局部變量即可,這也是最安全的預設行為。