Block 是 Objective-C 的閉包實現,將代碼塊作為一等公民傳遞,廣泛用於回調、GCD、動畫、枚舉等場景。本文系統梳理 Block 語法、變量捕獲規則、__block 修飾符、循環引用破解、複製語義與實戰模式。
// 完整聲明:返回類型 (^變量名)(參數列表) = ^返回類型(參數列表) { 函數體 };
int (^add)(int, int) = ^int(int a, int b) { return a + b; };
// 簡寫:返回類型可推導時省略
void (^logHello)(void) = ^{ NSLog(@"Hello"); };
// 類型別名,簡化複雜簽名
typedef double (^MathOp)(double, double);
MathOp multiply = ^(double x, double y) { return x * y; };
調用方式:與函數指針一致 add(3, 5),內部實際調用 Block_invoke 函數指針。
| 變量類型 | 捕獲方式 | 可否修改 | 存儲位置 | 生命周期 |
|---|---|---|---|---|
| 局部自動變量 (int a) | 值捕獲(拷貝) | 默認不可修改 | Block 結構體內部 | 隨 Block 複製而複製 |
| __block 修飾局部變量 | 引用捕獲(指向堆上封裝結構) | 可修改,修改對外可見 | 堆上 __Block_byref_結構 | 引用計數管理,Block 複製時 retain |
| 全局/靜態變量 | 直接訪問地址 | 可修改 | 數據段 | 程序生命周期 |
| self / 實例變量 | 捕獲 self 指針(強引用) | 可修改 ivar | 堆 | 導致循環引用風險 |
__block int counter = 0;
void (^increment)(void) = ^{
counter++; // 實際操作 __Block_byref_counter_0->__forwarding->value
};
increment();
NSLog(@"%d", counter); // 輸出 1,外部也可見修改
__forwarding 指針(指向自身,支持棧到堆的遷移)。Block_copy(block) 或 [block copy],ARC 下屬性聲明 @property (copy) void (^completion)(void); 自動處理。__weak typeof(self) weakSelf = self; 在 Block 內使用 weakSelf,必要時 __strong typeof(weakSelf) strongSelf = weakSelf; 保證執行期間不被釋放。@property (nonatomic, copy) void (^completionHandler)(BOOL success, NSError *error);
__weak typeof(self) weakSelf = self;
dispatch_async(queue, ^{
__strong typeof(self) strongSelf = weakSelf;
if (!strongSelf) return; // self 已釋放,放棄回調
[strongSelf updateUI];
});
- (void)fetchDataWithCompletion:(void (^)(id result))completion {
// 異步執行前 copy,確保生命周期超出當前作用域
void (^safeCompletion)(id) = [completion copy];
dispatch_async(backgroundQueue, ^{
id result = ...;
dispatch_async(dispatch_get_main_queue(), ^{
safeCompletion(result);
});
});
}
使用 self->ivar 會隱式捕獲 self。改用局部變量:id localIvar = self.ivar; 捕獲值,或通過 weakSelf 訪問屬性。
Block 實際是一個結構體:
struct Block_layout {
void *isa; // 指向 _NSConcreteStackBlock / _NSConcreteMallocBlock / _NSConcreteGlobalBlock
int flags; // 包含引用計數、是否有 __block 變量、是否需要幫助函數
int reserved;
void (*invoke)(void *, ...); // 函數指針,實際執行代碼
struct Block_descriptor *descriptor; // 大小、copy/dispose 輔助函數、簽名信息
// 捕獲的變量緊隨其後
};
_NSConcreteGlobalBlock:無捕獲或只捕獲全局/靜態變量,存放在數據段,無需 copy。_NSConcreteStackBlock:捕獲局部變量,存棧上,必須 copy。_NSConcreteMallocBlock:copy 後的堆 Block,引用計數管理。Block 是 Objective-C 現代化編程的核心。掌握捕獲規則、__block 語義、ARC copy/weak 模式,能寫出無洩漏、無崩潰、高可讀的異步代碼。記住三句口訣:屬性用 copy、捕獲用 weak、__block 共享可變狀態。