Block
问题
OC 中 Block 的本质是什么?Block 有几种类型?Block 如何捕获变量?__block 的作用原理?
答案
Block 的本质
Block 本质是一个封装了函数调用及其上下文的 OC 对象。编译器会将 Block 转换为一个结构体:
// OC 代码
int multiplier = 3;
int (^block)(int) = ^(int num) {
return num * multiplier;
};
// 编译后的结构体(简化)
struct __block_impl {
void *isa; // 指向类对象,证明 Block 是对象
int Flags;
int Reserved;
void *FuncPtr; // 指向实际执行的函数
};
struct __main_block_impl {
struct __block_impl impl;
int multiplier; // 捕获的变量(值拷贝)
};
Block 的三种类型
| 类型 | 存储区域 | 场景 |
|---|---|---|
__NSGlobalBlock__ | 数据段 | 不捕获外部变量 |
__NSStackBlock__ | 栈 | 捕获外部变量,未 copy |
__NSMallocBlock__ | 堆 | __NSStackBlock__ 被 copy 后 |
// GlobalBlock:不访问外部变量
void (^globalBlock)(void) = ^{
NSLog(@"hello");
};
NSLog(@"%@", [globalBlock class]); // __NSGlobalBlock__
// MallocBlock:ARC 下自动 copy 到堆
int x = 10;
void (^mallocBlock)(void) = ^{
NSLog(@"%d", x); // 捕获了外部变量
};
NSLog(@"%@", [mallocBlock class]); // __NSMallocBlock__
ARC 自动 copy
在 ARC 环境下,以下场景 Block 会自动从栈拷贝到堆:
- 赋值给
__strong变量 - 作为函数返回值
- 传入包含
usingBlock的 Cocoa API - 传入 GCD API
变量捕获
| 变量类型 | 捕获方式 | Block 内可修改 |
|---|---|---|
| 局部变量(基本类型) | 值拷贝 | ❌ |
| 局部变量(对象类型) | 指针拷贝(强引用) | 可修改对象属性,不可修改指针 |
__block 修饰的变量 | 指针引用(包装成结构体) | ✅ |
| 静态局部变量 | 指针引用 | ✅ |
| 全局变量 | 不捕获(直接访问) | ✅ |
int a = 10;
__block int b = 20;
static int c = 30;
void (^block)(void) = ^{
// a = 100; // ❌ 编译错误:值拷贝,不可修改
b = 200; // ✅ __block 变量可修改
c = 300; // ✅ 静态变量通过指针访问
};
__block 原理
__block 将变量包装成一个结构体对象,Block 捕获的是结构体的指针:
__block int age = 10;
// 编译后:age 被包装
struct __Block_byref_age {
void *__isa;
struct __Block_byref_age *__forwarding; // 指向自身(copy 后指向堆上的副本)
int age; // 实际的值
};
// Block 持有 __Block_byref_age 的指针
// 通过 __forwarding->age 访问,保证栈和堆都能访问到同一个值
__forwarding 指针的作用
当 __block 变量从栈拷贝到堆后:
- 栈上的
__forwarding→ 指向堆上的结构体 - 堆上的
__forwarding→ 指向自身
这样无论通过栈还是堆上的结构体访问,都能拿到堆上的最新值。
Block 循环引用
// ❌ 循环引用
self.block = ^{
[self doSomething]; // self → block → self
};
// ✅ __weak 打破
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) return;
[strongSelf doSomething];
};
// ✅ __block 打破(需要手动置 nil)
__block ViewController *blockSelf = self;
self.block = ^{
[blockSelf doSomething];
blockSelf = nil; // 执行后打破循环
};
常见面试问题
Q1: Block 为什么用 copy 修饰?
答案:
MRC 时代,Block 默认在栈上,赋值给属性时需要 copy 到堆上以延长生命周期。ARC 下 strong 和 copy 效果一样(编译器自动 copy),但用 copy 是历史惯例,也能明确表达语义。
Q2: 在 Block 中修改 NSMutableArray 需要 __block 吗?
答案:
不需要。__block 用于修改指针本身。对可变集合的 addObject:、removeObject: 等操作是修改对象内容(通过指针调用方法),不需要修改指针,所以不需要 __block。
NSMutableArray *arr = [NSMutableArray array];
void (^block)(void) = ^{
[arr addObject:@"item"]; // ✅ 不需要 __block
// arr = [NSMutableArray array]; // ❌ 需要 __block 才能修改指针本身
};
Q3: Block 中使用 weakSelf 后还需要 strongSelf 吗?
答案:
需要(在异步场景中)。weakSelf 可能在 Block 执行过程中变为 nil,导致后续操作无效甚至崩溃。strongSelf 确保在 Block 执行期间 self 不会被释放。Block 执行完毕后 strongSelf 就是局部变量会自动释放,不会造成循环引用。