Block:Objective-C 的閉包實作,捕獲上下文的匿名函數
Block 是 iOS/macOS 開發核心技術,廣泛用於枚舉、動畫、排序、網路回調、多執行緒。本文系統整理語法、捕獲語意、記憶體管理與最佳實務。
一、Block 語法基礎
完整宣告語法
返回類型 (^Block名稱)(參數列表) = ^返回類型(參數列表) { 實作代碼 };
常見形式
// 無參無回傳
void (^simpleBlock)(void) = ^{ NSLog(@"Hello"); };
// 有參有回傳
int (^addBlock)(int, int) = ^int(int a, int b) { return a + b; };
// 簡寫:無參時 ^ 後可省略 ()
void (^block)() = ^{ ... };
// 呼叫
simpleBlock();
int sum = addBlock(3, 5); // 8
二、Block 作為參數傳遞 (最常見用法)
NSArray *array = @[@"張三", @"李四", @"王五", @"趙六"];
// enumerateObjectsUsingBlock:
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSLog(@"第 %lu 項: %@", idx, obj);
if ([@"王五" isEqualToString:obj]) {
*stop = YES; // 提前結束枚舉
}
}];
三、變數捕獲規則
| 變數類型 | 捕獲方式 | 可否修改 | 說明 |
| 區域變數 (自動) | 值捕獲 (複製) | 預設唯讀 | Block 建立時複製當前值 |
| __block 區域變數 | 引用捕獲 | 可修改 | 共享同一記憶體位置 |
| 全域/靜態變數 | 引用捕獲 | 可修改 | 直接存取原位址 |
| 物件指標 | 強引用 (retain) | 可修改指向 | Block 持有物件 |
| self (隱含) | 強引用 | - | 易造成循環引用 |
__block 修飾符範例
__block int counter = 0;
__block BOOL found = NO;
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
counter++; // 可修改
if ([@"目標" isEqualToString:obj]) {
found = YES;
*stop = YES;
}
}];
NSLog(@"總檢查 %d 項,找到: %@", counter, found ? @"是" : @"否");
四、typedef 簡化 Block 類型宣告
typedef double (^MathBlock)(double, double);
MathBlock area = ^(double w, double h) { return w * h; };
MathBlock sum = ^(double a, double b) { return a + b; };
NSLog(@"面積: %.2f", area(10, 20)); // 200.00
NSLog(@"加總: %.2f", sum(10, 20)); // 30.00
五、Block 加入集合 (陣列/字典)
@property (nonatomic, strong) NSMutableArray *blocks;
- (void)setupBlocks {
int (^add)(int, int) = ^(int x, int y) { return x + y; };
[self.blocks addObject:add]; // Block 自動 copy 到堆上
int (^mul)(int, int) = ^(int x, int y) { return x * y; };
[self.blocks addObject:mul];
}
- (int)executeBlockAtIndex:(NSUInteger)idx withX:(int)x y:(int)y {
int (^block)(int, int) = self.blocks[idx];
return block(x, y);
}
六、循環引用問題與解決
問題場景
// self 持有 blocks 陣列
// block 內呼叫 self.method -> block 持有 self
// 形成:self → blocks → block → self (強引用環)
@property (nonatomic, strong) NSMutableArray *blocks;
- (void)badExample {
int (^block)(int, int) = ^(int x, int y) {
return [self compute:x y:y]; // 捕獲 self (強引用)
};
[self.blocks addObject:block];
// self 永不釋放!
}
解決方案:__weak 弱引用
- (void)goodExample {
__weak typeof(self) weakSelf = self; // 或 __weak SelfType *weakSelf = self;
int (^block)(int, int) = ^(int x, int y) {
// 使用 weakSelf,Block 不持有 self
return [weakSelf compute:x y:y];
};
[self.blocks addObject:block];
// 進階:在 block 內轉強引用確保執行期間不被釋放
int (^safeBlock)(int, int) = ^(int x, int y) {
__strong typeof(self) strongSelf = weakSelf;
if (strongSelf) {
return [strongSelf compute:x y:y];
}
return 0; // self 已釋放
};
}
七、Block 在 iOS 開發中的核心應用場景
| 場景 | API 範例 | 用途 |
| 集合枚舉 | enumerateObjectsUsingBlock: | 替代 for 迴圈,支援提前停止 |
| 視圖動畫 | animateWithDuration:animations: | 聲明式動畫配置 |
| 排序 | sortedArrayUsingComparator: | 自訂比較邏輯 |
| 通知中心 | addObserverForName:object:queue:usingBlock: | 區域性通知處理 |
| 錯誤處理 | completionHandler (NSError *) | 非同步結果回傳 |
| 多執行緒 | dispatch_async(queue, ^{...}) | GCD 任務提交 |
| 網路請求 | dataTaskWithRequest:completionHandler: | 非同步回調 |
八、記憶體管理核心規則
- Block 建立在棧上:離開作用域即銷毀
- copy 移至堆上:
[block copy] 或屬性 copy 修飾
- Block 捕獲物件 → 強引用:需警惕循環引用
- __block 變數 → 堆上共享結構:多個 Block 可共享修改
- ARC 自動處理 copy:傳遞給 API 時自動 copy,但手動存屬性需宣告
copy
九、除錯技巧
- Instruments → Leaks:檢測 Block 循環引用洩漏
- Debug Memory Graph:Xcode 8+ 視覺化引用關係
- 靜態分析:
Product → Analyze 偵測潛在循環引用
- Block 斷點:在 Block 內打斷點檢查捕獲變數值