基于本文回答

播面 播面

刷题像听歌,多听自然懂
0
评论

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)
  • 机制(底层原理):
    1. 当变量被 __block 修饰时,编译器会将该变量包装成一个对象(结构体 byref)。
    2. Block 会持有指向这个结构体的指针。
    3. 当 Block 从栈复制到堆(Copy)时,这个 byref 结构体也会被移动到堆上。
    4. 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 打破循环引用。
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 变量:把人搬到了一个共享的房子里(堆),大家一起操作。
00:00
00:00