Block 中如何截获变量?
在 Objective-C 中,Block 是一个封装了函数调用以及函数调用环境的 Objective-C 对象。Block 对变量的“截获”(Capture)机制取决于变量的类型。
主要分为以下几种情况:
1. 局部变量(Automatic Variables)
这是最常见的情况(例如在方法内部定义的 int a = 10;)。
- 截获方式:值截获 (Value Capture)
- 机制: Block 在创建时,会将局部变量的当前值复制一份到 Block 的结构体内部。
- 特点:
- Block 内部持有的是变量的副本。
- 只读: 在 Block 内部不能修改该变量的值(编译器会报错),因为修改副本不会影响外部变量,逻辑上容易造成混淆。
- 时机: 截获的是 Block 定义时刻的值,而不是调用时刻的值。
plaintext
int age = 10;
void (^block)(void) = ^{
// age = 20; // 报错:Variable is not assignable (missing __block type specifier)
NSLog(@"Age: %d", age);
};
age = 20;
block(); // 输出 Age: 10
2. 静态局部变量(Static Local Variables)
在方法内部用 static 修饰的变量。
- 截获方式:指针截获 (Pointer Capture)
- 机制: Block 内部持有该变量的内存地址(指针)。
- 特点:
- 可读写: 因为持有的是地址,所以在 Block 内部可以修改变量的值,且会影响外部。
- 同步: 外部修改也会影响 Block 内部读取的值。
plaintext
static int count = 10;
void (^block)(void) = ^{
count = 20; // 可以修改
NSLog(@"Count: %d", count);
};
count = 30;
block(); // 输出 Count: 20 (Block内部修改生效,且读取时是最新的内存值)
// 再次打印外部 count,此时为 20
3. 全局变量(Global Variables)
包括全局静态变量和普通全局变量。
- 截获方式:不截获 (Direct Access)
- 机制: Block 直接访问全局数据区的内存。
- 特点:
- Block 结构体中不保存这些变量的信息。
- 可以在 Block 内部直接读取和修改。
4. __block 修饰的变量
当我们需要在 Block 内部修改局部变量时,需要加上 __block 修饰符。
- 截获方式:引用截获 (By Reference / Forwarding)
- 机制(底层原理):
- 当变量被
__block修饰时,编译器会将该变量包装成一个对象(结构体byref)。 - Block 会持有指向这个结构体的指针。
- 当 Block 从栈复制到堆(Copy)时,这个
byref结构体也会被移动到堆上。 - Block 内部通过指针找到这个结构体进行修改,从而实现“修改外部变量”的效果。
- 当变量被
- 特点:
- 可读写: 可以在 Block 内部修改值,外部也会同步变化。
plaintext
__block int number = 10;
void (^block)(void) = ^{
number = 20;
};
block();
NSLog(@"Number: %d", number); // 输出 Number: 20
5. 对象类型的局部变量
例如 NSString *str = @"hello"; 或 self。
- 截获方式:值截获 + 连带引用计数处理
- 机制:
- Block 会截获对象的指针(即值截获指针变量)。
- 内存管理: 如果 Block 被拷贝到堆上(Heap Block),它会强引用(Retain) 被截获的对象。当 Block 销毁时,会释放(Release)该对象。
- 关于
self的隐式截获:- 如果在 Block 内部使用了实例变量(如
_name)或属性(self.name),Block 会自动截获self。 - 风险: 如果
self持有 Block,而 Block 又截获了self,就会造成循环引用(Retain Cycle),导致内存泄漏。 - 解决: 使用
__weak打破循环引用。
- 如果在 Block 内部使用了实例变量(如
plaintext
// 循环引用示例
self.myBlock = ^{
NSLog(@"%@", self.name); // 强引用 self
};
// 解决方案
__weak typeof(self) weakSelf = self;
self.myBlock = ^{
__strong typeof(weakSelf) strongSelf = weakSelf; // 防止执行期间 self 释放
NSLog(@"%@", strongSelf.name);
};
总结表
| 变量类型 | 截获方式 | Block 内是否可修改 | 备注 |
|---|---|---|---|
| 局部变量 | 值截获 (Copy值) | 否 | 相当于 const 拷贝,截获定义时的瞬时值 |
| 静态局部变量 | 指针截获 (Copy地址) | 是 | 指向同一块内存 |
| 全局变量 | 不截获 | 是 | 直接访问全局内存 |
| __block 变量 | 引用截获 (Copy结构体指针) | 是 | 变量被包装成对象移至堆上 |
| 对象变量 | 值截获 (Copy指针) | 是(指修改对象属性) | Block 会强引用该对象 (Retain) |
简单记忆法
- 局部变量:拍照(只拿到了照片,改不了本人)。
- 静态变量:拿到了家庭住址(可以直接去家里找人)。
__block变量:把人搬到了一个共享的房子里(堆),大家一起操作。