Block 分类、变量捕获与作用域
一、Block 的分类
Block 在 Objective-C 中,根据其存储位置和生命周期分为以下三类:
Block 类型 |
存储位置 |
特点说明 |
Global Block |
全局区 |
不捕获外部变量 |
Stack Block |
栈上 |
捕获外部变量,随栈帧销毁 |
Heap Block |
堆上 |
捕获外部变量,Block_copy 后存在堆上 |
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13
| void (^block1)(void) = ^{ NSLog(@"Hello"); };
int a = 10; void (^block2)(void) = ^{ NSLog(@"a = %d", a); };
void (^block3)(void) = [block2 copy];
|
二、变量捕获(Captured Variables)
1. 默认捕获(值语义)
Block 默认以值方式拷贝局部变量,不可修改。
1 2 3 4 5 6
| int a = 5; void (^block)(void) = ^{ NSLog(@"a is %d", a); }; a = 6; block();
|
2. __block
修饰(引用语义)
允许在 Block 内部修改外部变量。
1 2 3 4 5 6
| __block int a = 5; void (^block)(void) = ^{ a = 10; }; block(); NSLog(@"a is %d", a);
|
3. 对象捕获(指针语义)
对象变量以指针形式捕获,会被强引用。
1 2 3 4 5
| NSString *str = @"Hello"; void (^block)(void) = ^{ NSLog(@"%@", str); };
|
三、作用域与内存管理
生命周期:
- Stack Block 生命周期短,超出作用域会被销毁。
- 需要异步使用时,必须将 Block copy 到堆上,变为 Heap Block。
- ARC 下自动进行
copy
操作;MRC 需手动调用 Block_copy
。
示例(ARC 异步安全):
1 2 3 4
| dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"Block executed"); });
|
四、Swift 中的闭包捕获
Swift 中闭包默认强引用捕获对象:
1 2 3 4 5 6 7 8
| class MyClass { var name = "Test" func doSomething() { DispatchQueue.global().async { [weak self] in print(self?.name ?? "nil") } } }
|
使用 [weak self]
/ [unowned self]
可避免循环引用。
五、小结对比表
分类 |
是否捕获变量 |
存储位置 |
生命周期控制 |
Global Block |
否 |
全局区 |
程序生命周期内常驻 |
Stack Block |
是 |
栈上 |
随栈帧销毁 |
Heap Block |
是 |
堆上 |
Block_copy 后存在于堆中 |