跳到主要内容

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 会自动从栈拷贝到堆:

  1. 赋值给 __strong 变量
  2. 作为函数返回值
  3. 传入包含 usingBlock 的 Cocoa API
  4. 传入 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 下 strongcopy 效果一样(编译器自动 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 就是局部变量会自动释放,不会造成循环引用。

相关链接