Block (閉包) 是 Objective-C 2.0 引入的匿名函數塊,賦予 C 語言函數式編程能力,也是 GCD、動畫、網絡回調、枚舉器的核心載體。本文系統講解聲明定義語法、變量捕獲規則、__block 修飾符、複製機制、ARC/MRC 內存管理、類型定義與 GCD 實戰模式。
| 形式 | 語法 | 示例 |
| 聲明變量 | 返回類型 (^名稱)(參數列表); | int (^add)(int, int); |
| 定義字面量 | ^返回類型(參數) { 體 } | ^int(int a, int b) { return a+b; } |
| 類型定義 | typedef 返回類型 (^類型名)(參數); | typedef int (^AddBlock)(int, int); |
| 作為參數 | void func(AddBlock block); | func(^int(int a,int b){return a+b;}); |
| 作為返回值 | AddBlock makeAdder(int base); | 見下文 |
二、變量捕獲語義
void demo_capture() {
int a = 10;
const int b = 20;
static int c = 30;
int arr[3] = {1,2,3};
// 1. 自動變量:值捕獲 (只讀複制)
void (^blk1)() = ^{
NSLog(@"a=%d, b=%d, c=%d, arr[0]=%d", a, b, c, arr[0]);
// a=10, b=20, c=30, arr[0]=1 (編譯時複制)
};
a = 100; // 修改不影響 blk1 內的副本
blk1(); // 仍輸出 10
// 2. __block 修飾:引用捕獲 (可讀寫共享)
__block int x = 42;
void (^blk2)() = ^{
x = 100;
NSLog(@"x=%d", x); // 100
};
blk2();
NSLog(@"outer x=%d", x); // 100,外部也變了
// 3. 靜態/全局變量:直接引用 (無需 __block)
// 4. 數組/結構體:退化為指針,捕獲地址 (淺捕獲)
}
捕獲規則總結
| 變量類型 | 默認捕獲 | 可修改 | __block 效果 |
| 自動局部變量 (auto) | 值捕獲 (複制) | ❌ | 引用捕獲 (共享存儲) |
| static / 全局 | 引用捕獲 (地址) | ✅ | 無效果 (已是引用) |
| const 自動變量 | 值捕獲 | ❌ | 引用捕獲 (可去 const) |
| 數組 / 結構體 | 指針捕獲 (淺) | ✅ 修改內容 | 指針本身可重綁定 |
三、Block 內存管理:棧→堆複製
// Block 字面量初始在棧上 (栈 Block)
// 超出作用域必須 copy 到堆
void (^stackBlk)() = ^{ NSLog(@"stack"); };
// ARC 環境:自動處理
// - 作為參數傳遞/賦值給強屬性 → 自動 copy
// - 返回 Block → 編譯器插入 copy (需標註 NS_RETURNS_RETAINED 或靜態分析)
// MRC 環境:手動管理
void (^heapBlk)() = [stackBlk copy]; // 保留塊
[heapBlk release]; // 釋放
// 屬性聲明
@property (nonatomic, copy) void (^completion)(void); // copy 語義必須
複製規則口訣
- 棧 Block:作用域結束銷毀,離開定義作用域前必須 copy。
- 全局 Block:字面量在數據段,copy 無效果 (no-op)。
- ARC:
copy 屬性、強引用賦值、跨函數返回 → 自動 copy。
- MRC:
Block_copy() / Block_release() 配對。
四、__block 底層實現與循環引用防範
// __block 變量被包裝為堆對象
// 結構體包含:轉發指針、引用計數、實際值
// Block copy 時會 retain __block 對象
// Block release 時 release __block 對象
// 循環引用典型場景
__block typeof(self) weakSelf = self; // MRC/手動弱引用
self.completion = ^{
[weakSelf doSomething]; // 不 retain self
};
// ARC 環境推薦
__weak typeof(self) weakSelf = self;
self.completion = ^{
__strong typeof(self) strongSelf = weakSelf;
if (strongSelf) [strongSelf doSomething];
};
五、GCD 實戰模式
// 1. 異步任務 + 主線程回調
dispatch_async(dispatch_get_global_queue(0,0), ^{
id result = [self heavyComputation];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateUIWithResult:result];
});
});
// 2. 併發組等待多任務
dispatch_group_t group = dispatch_group_create();
for (NSURL *url in urls) {
dispatch_group_enter(group);
[self downloadURL:url completion:^{
dispatch_group_leave(group);
}];
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"All done");
});
// 3. 信號量限制併發度
dispatch_semaphore_t sema = dispatch_semaphore_create(3);
for (id task in tasks) {
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{
[task execute];
dispatch_semaphore_signal(sema);
});
}
六、Block 與 C++ Lambda 對比
| 特性 | ObjC Block | C++ Lambda |
| 捕獲語法 | 隱式值捕獲 + __block | 顯式 [=], [&], [x] |
| 可變捕獲 | __block | mutable 關鍵字 |
| 內存模型 | 棧→堆 copy、ARC/MRC 管理 | 值語義、自動析構、std::function 擦除類型 |
| 類型擦除 | id / void (^)(void) | std::function |
| 異步框架 | GCD / NSOperation | std::thread / std::async / 協程 |
七、避坑清單
- Block 內強引用 self → 循環引用,用
__weak 打破。
- Block 字面量直接賦值給 weak 屬性 → 即刻銷毀,必須先 copy 或用強引用中轉。
- __block 變量在多線程並發修改 → 數據競態,需加鎖或用原子操作。
- Block 作為 C 回調傳遞:轉換為函數指針不可行,需包裝上下文結構體 + trampoline 函數。
- 調試時打印 Block:
po block 顯示簽名,p (void*)block 看地址判斷棧/堆。
Block 將「代碼作為數據」在 Objective-C 落地,配合 GCD 構建了現代 iOS/macOS 異步編程的基石。掌握捕獲語義、複製機制、弱引用打破循環,是寫出無崩潰、無洩漏並發代碼的必修課。
- 上一篇文章: C語言獲取文件大小三種方法:fseek/ftell、stat 結構體、Windows API 對比
- 下一篇文章: C語言 extern 關鍵字詳解:跨文件變數共享、聲明與定義、項目級最佳實踐
Copyright ©
程式碼之家 All Rights Reserved