文章目录
-
- 前言
- [一、Block 的本质](#一、Block 的本质)
-
- [1.1 Block 是什么?](#1.1 Block 是什么?)
- [1.2 使用 Clang 查看底层结构](#1.2 使用 Clang 查看底层结构)
- [1.3 Block 的内存布局](#1.3 Block 的内存布局)
- [二、Block 的三种类型](#二、Block 的三种类型)
-
- [2.1 类型分类](#2.1 类型分类)
- [2.2 代码验证](#2.2 代码验证)
- [2.3 ARC 下的自动 copy](#2.3 ARC 下的自动 copy)
- [2.4 Block 类型继承关系](#2.4 Block 类型继承关系)
- 三、变量捕获机制
-
- [3.1 捕获规则总览](#3.1 捕获规则总览)
- [3.2 局部变量的捕获(值捕获)](#3.2 局部变量的捕获(值捕获))
- [3.3 对象类型的捕获](#3.3 对象类型的捕获)
- [3.4 静态局部变量的捕获(指针捕获)](#3.4 静态局部变量的捕获(指针捕获))
- [3.5 全局变量不捕获](#3.5 全局变量不捕获)
- [四、__block 修饰符深度解析](#四、__block 修饰符深度解析)
-
- [4.1 为什么需要 __block?](#4.1 为什么需要 __block?)
- [4.2 __block 的底层结构](#4.2 __block 的底层结构)
- [4.3 __forwarding 指针的作用](#4.3 __forwarding 指针的作用)
- [4.4 __block 变量的内存管理](#4.4 __block 变量的内存管理)
- [4.5 __block 修饰对象类型](#4.5 __block 修饰对象类型)
- [五、Block 的 Copy 操作详解](#五、Block 的 Copy 操作详解)
-
- [5.1 不同类型 Block 的 Copy 行为](#5.1 不同类型 Block 的 Copy 行为)
- [5.2 Copy 操作源码分析](#5.2 Copy 操作源码分析)
- [5.3 Copy 对捕获变量的影响](#5.3 Copy 对捕获变量的影响)
- 六、循环引用问题与解决方案
-
- [6.1 循环引用的产生](#6.1 循环引用的产生)
- [6.2 解决方案一:__weak](#6.2 解决方案一:__weak)
- [6.3 解决方案二:__block + 手动置 nil](#6.3 解决方案二:__block + 手动置 nil)
- [6.4 解决方案三:使用参数传递](#6.4 解决方案三:使用参数传递)
- [6.5 各方案对比](#6.5 各方案对比)
- 七、实战案例分析
-
- [7.1 案例一:网络请求回调](#7.1 案例一:网络请求回调)
- [7.2 案例二:Timer 回调](#7.2 案例二:Timer 回调)
- [7.3 案例三:Block 嵌套](#7.3 案例三:Block 嵌套)
- [7.4 案例四:使用 __block 修改状态](#7.4 案例四:使用 __block 修改状态)
- [八、Block 内存管理总结](#八、Block 内存管理总结)
-
- [8.1 核心要点图解](#8.1 核心要点图解)
- [8.2 面试常见问题](#8.2 面试常见问题)
- 九、调试技巧
-
- [9.1 LLDB 调试命令](#9.1 LLDB 调试命令)
- [9.2 检测循环引用](#9.2 检测循环引用)
- 总结
- 参考资料
前言
Block 是 iOS 开发中极其重要的特性,它本质上是一个带有自动变量的匿名函数。理解 Block 的底层实现,不仅能帮助我们写出更高效的代码,还能有效避免循环引用等常见问题。
本文将从编译器视角,深入剖析 Block 的底层结构、变量捕获机制以及 __block 修饰符的实现原理。
一、Block 的本质
1.1 Block 是什么?
Block 在编译后会被转换为一个结构体对象,它包含:
- 函数指针(指向 Block 执行的代码)
- 捕获的变量
- Block 的描述信息
objc
// 我们写的代码
void (^block)(void) = ^{
NSLog(@"Hello Block");
};
block();
1.2 使用 Clang 查看底层结构
bash
# 将 OC 代码转换为 C++ 代码
clang -rewrite-objc main.m -o main.cpp
# 如果有系统框架依赖,使用以下命令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-14.0.0 main.m -o main.cpp
转换后的核心结构:
cpp
// Block 的通用结构
struct __block_impl {
void *isa; // isa 指针,指向 Block 的类型
int Flags; // 标志位
int Reserved; // 保留字段
void *FuncPtr; // 函数指针,指向 Block 执行的代码
};
// 具体 Block 的结构(以 main 函数中的 block 为例)
struct __main_block_impl_0 {
struct __block_impl impl; // Block 基础结构
struct __main_block_desc_0* Desc; // Block 描述信息
// 构造函数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// Block 执行的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog(@"Hello Block");
}
// Block 描述信息
static struct __main_block_desc_0 {
size_t reserved; // 保留字段
size_t Block_size; // Block 大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0) };
1.3 Block 的内存布局
┌─────────────────────────────────────────────────────────────────────┐
│ Block 内存布局 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ struct __main_block_impl_0 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ struct __block_impl impl │ │
│ │ ┌───────────────────────────────────────────────────────┐ │ │
│ │ │ void *isa → 指向 Block 类型 │ │ │
│ │ │ int Flags → 标志位 │ │ │
│ │ │ int Reserved → 保留 │ │ │
│ │ │ void *FuncPtr → 指向 __main_block_func_0 │ │ │
│ │ └───────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ struct __main_block_desc_0 *Desc → 描述信息指针 │ │
│ │ │ │
│ │ // 捕获的变量(如果有) │ │
│ │ int captured_var; │ │
│ │ id captured_obj; │ │
│ │ __Block_byref_xxx *byref_var; // __block 变量 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
二、Block 的三种类型
2.1 类型分类
| 类型 | 类名 | 存储位置 | 触发条件 |
|---|---|---|---|
| 全局 Block | _NSConcreteGlobalBlock |
数据段 | 不捕获任何变量 |
| 栈 Block | _NSConcreteStackBlock |
栈区 | 捕获了变量,但未 copy |
| 堆 Block | _NSConcreteMallocBlock |
堆区 | 栈 Block 调用 copy 后 |
2.2 代码验证
objc
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 1. 全局 Block - 不捕获任何变量
void (^globalBlock)(void) = ^{
NSLog(@"Global Block");
};
NSLog(@"Global Block: %@", [globalBlock class]);
// 输出: __NSGlobalBlock__
// 2. 栈 Block - 捕获了变量(MRC 下)
int a = 10;
void (^stackBlock)(void) = ^{
NSLog(@"Stack Block: %d", a);
};
NSLog(@"Stack Block: %@", [stackBlock class]);
// MRC 输出: __NSStackBlock__
// ARC 输出: __NSMallocBlock__ (ARC 自动 copy)
// 3. 堆 Block - copy 后的 Block
void (^mallocBlock)(void) = [stackBlock copy];
NSLog(@"Malloc Block: %@", [mallocBlock class]);
// 输出: __NSMallocBlock__
}
return 0;
}
2.3 ARC 下的自动 copy
ARC 环境下,以下情况会自动将栈 Block copy 到堆:
objc
// 1. Block 作为函数返回值
typedef void(^MyBlock)(void);
MyBlock getBlock() {
int a = 10;
return ^{ // 自动 copy 到堆
NSLog(@"%d", a);
};
}
// 2. Block 被强引用
int value = 20;
self.myBlock = ^{ // 赋值给 strong 属性,自动 copy
NSLog(@"%d", value);
};
// 3. Block 作为 Cocoa API 中带有 usingBlock 的方法参数
NSArray *array = @[@1, @2, @3];
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// 自动 copy
}];
// 4. Block 作为 GCD API 的参数
dispatch_async(dispatch_get_main_queue(), ^{
// 自动 copy
});
2.4 Block 类型继承关系
┌─────────────────────────────────────────────────────────────────────┐
│ Block 类型继承链 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ NSObject │
│ │ │
│ ▼ │
│ NSBlock │
│ │ │
│ ┌────────────────┼────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ __NSGlobalBlock __NSStackBlock __NSMallocBlock │
│ (数据段) (栈区) (堆区) │
│ │
│ 继承验证代码: │
│ [globalBlock isKindOfClass:[NSObject class]] → YES │
│ [[globalBlock class] superclass] → __NSGlobalBlock │
│ [[[globalBlock class] superclass] superclass] → NSBlock │
│ [[[[globalBlock class] superclass] superclass] superclass] │
│ → NSObject │
│ │
└─────────────────────────────────────────────────────────────────────┘
三、变量捕获机制
3.1 捕获规则总览
┌─────────────────────────────────────────────────────────────────────┐
│ 变量捕获规则 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 变量类型 │ 捕获方式 │ Block 内能否修改 │
│ ─────────────────────┼──────────────────┼──────────────────── │
│ 局部基本类型变量 │ 值捕获(拷贝) │ ❌ 不能修改原值 │
│ 局部对象类型变量 │ 指针捕获(拷贝) │ ❌ 不能修改指针 │
│ __block 修饰的变量 │ 指针捕获(引用) │ ✅ 可以修改 │
│ 静态局部变量 │ 指针捕获 │ ✅ 可以修改 │
│ 全局变量 │ 不捕获(直接访问) │ ✅ 可以修改 │
│ 静态全局变量 │ 不捕获(直接访问) │ ✅ 可以修改 │
│ │
└─────────────────────────────────────────────────────────────────────┘
3.2 局部变量的捕获(值捕获)
objc
// 原始代码
int main() {
int age = 20;
void (^block)(void) = ^{
NSLog(@"age = %d", age);
};
age = 30;
block(); // 输出: age = 20
return 0;
}
编译后的 C++ 代码:
cpp
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age; // ⭐ 捕获的变量成为结构体成员
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc,
int _age, int flags=0) : age(_age) { // ⭐ 值拷贝
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // ⭐ 使用捕获的值
NSLog(@"age = %d", age);
}
int main() {
int age = 20;
// 创建 Block 时,将 age 的值(20)传入构造函数
void (*block)(void) = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA,
age // ⭐ 值传递
);
age = 30; // 修改原变量,不影响 Block 内的值
// 执行 Block
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
图解:
┌─────────────────────────────────────────────────────────────────────┐
│ 局部变量值捕获 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 创建 Block 时: │
│ │
│ 栈帧 Block 结构体 │
│ ┌─────────────┐ ┌─────────────────┐ │
│ │ int age = 20│──── 值拷贝 ──→│ int age = 20 │ │
│ └─────────────┘ │ (结构体成员) │ │
│ └─────────────────┘ │
│ │
│ 修改原变量后: │
│ │
│ 栈帧 Block 结构体 │
│ ┌─────────────┐ ┌─────────────────┐ │
│ │ int age = 30│ │ int age = 20 │ ← 不受影响 │
│ └─────────────┘ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
3.3 对象类型的捕获
objc
// 原始代码
int main() {
NSMutableArray *array = [NSMutableArray array];
void (^block)(void) = ^{
[array addObject:@"item"]; // ✅ 可以修改对象内容
NSLog(@"array = %@", array);
};
block();
return 0;
}
编译后:
cpp
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSMutableArray *array; // ⭐ 捕获的是指针(地址值的拷贝)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc,
NSMutableArray *_array, int flags=0) : array(_array) {
// ...
}
};
// ARC 下会自动添加 copy 和 dispose 函数
static void __main_block_copy_0(struct __main_block_impl_0 *dst,
struct __main_block_impl_0 *src) {
_Block_object_assign(&dst->array, src->array, BLOCK_FIELD_IS_OBJECT);
}
static void __main_block_dispose_0(struct __main_block_impl_0 *src) {
_Block_object_dispose(src->array, BLOCK_FIELD_IS_OBJECT);
}
图解:
┌─────────────────────────────────────────────────────────────────────┐
│ 对象类型捕获 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 栈帧 堆区 │
│ ┌──────────────────┐ ┌────────────────────┐ │
│ │ NSMutableArray │ │ NSMutableArray │ │
│ │ *array = 0x1234 │────┬────→│ 对象实例 │ │
│ └──────────────────┘ │ │ retainCount++ │ │
│ │ └────────────────────┘ │
│ Block 结构体 │ ↑ │
│ ┌──────────────────┐ │ │ │
│ │ NSMutableArray │ │ │ │
│ │ *array = 0x1234 │────┴──────────────┘ │
│ │ (指针值的拷贝) │ 两个指针指向同一个对象 │
│ └──────────────────┘ 所以可以修改对象内容 │
│ │
│ ⚠️ 但不能修改指针本身: │
│ array = [NSMutableArray new]; // ❌ 错误,不会影响外部变量 │
│ │
└─────────────────────────────────────────────────────────────────────┘
3.4 静态局部变量的捕获(指针捕获)
objc
// 原始代码
int main() {
static int count = 0;
void (^block)(void) = ^{
count++; // ✅ 可以修改
NSLog(@"count = %d", count);
};
block(); // 输出: count = 1
block(); // 输出: count = 2
return 0;
}
编译后:
cpp
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *count; // ⭐ 捕获的是指针(地址)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc,
int *_count, int flags=0) : count(_count) {
// ...
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *count = __cself->count; // 获取指针
(*count)++; // ⭐ 通过指针修改值
NSLog(@"count = %d", (*count));
}
int main() {
static int count = 0;
void (*block)(void) = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA,
&count // ⭐ 传入地址
);
// ...
}
3.5 全局变量不捕获
objc
// 全局变量
int globalVar = 100;
static int staticGlobalVar = 200;
int main() {
void (^block)(void) = ^{
globalVar = 101; // ✅ 直接访问全局变量
staticGlobalVar = 201; // ✅ 直接访问静态全局变量
};
block();
return 0;
}
编译后:
cpp
int globalVar = 100;
static int staticGlobalVar = 200;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
// ⭐ 没有捕获任何变量
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
globalVar = 101; // ⭐ 直接访问全局变量
staticGlobalVar = 201; // ⭐ 直接访问静态全局变量
}
为什么全局变量不需要捕获?
┌─────────────────────────────────────────────────────────────────────┐
│ 为什么全局变量不捕获 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 内存布局: │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 代码段 (.text) │ │
│ ├─────────────────────────────────────────────────────────────┤ │
│ │ 数据段 (.data) │ │
│ │ ┌───────────────────────────────────────────────────────┐ │ │
│ │ │ globalVar = 100 ← 全局变量 │ │ │
│ │ │ staticGlobalVar = 200 ← 静态全局变量 │ │ │
│ │ │ _NSConcreteGlobalBlock ← 全局 Block │ │ │
│ │ └───────────────────────────────────────────────────────┘ │ │
│ ├─────────────────────────────────────────────────────────────┤ │
│ │ 堆区 (heap) │ │
│ ├─────────────────────────────────────────────────────────────┤ │
│ │ 栈区 (stack) │ │
│ │ ┌───────────────────────────────────────────────────────┐ │ │
│ │ │ 局部变量(执行完毕后销毁) │ │ │
│ │ └───────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 解释: │
│ • 全局变量在程序整个运行期间都存在 │
│ • 任何位置都可以直接通过符号名访问 │
│ • 不存在"过了作用域就销毁"的问题 │
│ • 所以 Block 不需要捕获,直接访问即可 │
│ │
│ 局部变量需要捕获是因为: │
│ • 局部变量在栈上,函数返回后会被销毁 │
│ • Block 可能在变量销毁后才执行 │
│ • 所以需要"捕获"(保存一份)以保证 Block 执行时变量仍然有效 │
│ │
└─────────────────────────────────────────────────────────────────────┘
四、__block 修饰符深度解析
4.1 为什么需要 __block?
objc
// 问题:无法在 Block 内修改局部变量
int a = 10;
void (^block)(void) = ^{
a = 20; // ❌ 编译错误:Variable is not assignable
};
// 解决:使用 __block
__block int b = 10;
void (^block2)(void) = ^{
b = 20; // ✅ 可以修改
};
block2();
NSLog(@"%d", b); // 输出: 20
4.2 __block 的底层结构
objc
// 原始代码
__block int age = 20;
void (^block)(void) = ^{
age = 30;
NSLog(@"age = %d", age);
};
block();
编译后的结构:
cpp
// ⭐ __block 变量被包装成一个结构体
struct __Block_byref_age_0 {
void *__isa; // isa 指针
__Block_byref_age_0 *__forwarding; // ⭐ 指向自己(关键!)
int __flags; // 标志位
int __size; // 结构体大小
int age; // ⭐ 实际存储的值
};
// Block 结构体
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // ⭐ 捕获的是指针
};
// Block 执行函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_age_0 *age = __cself->age;
// ⭐ 通过 __forwarding 访问值
(age->__forwarding->age) = 30;
NSLog(@"age = %d", (age->__forwarding->age));
}
// main 函数
int main() {
// ⭐ 创建 __block 变量的包装结构体
__Block_byref_age_0 age = {
0, // __isa
&age, // __forwarding 指向自己
0, // __flags
sizeof(__Block_byref_age_0), // __size
20 // age 初始值
};
void (*block)(void) = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA,
&age, // 传入 __Block_byref_age_0 的地址
0x22000000
);
// 执行 Block
block->FuncPtr(block);
return 0;
}
4.3 __forwarding 指针的作用
核心问题 :当 Block 从栈 copy 到堆时,__block 变量也会被 copy 到堆。此时栈和堆上都有一份 __Block_byref 结构体,如何保证访问的是同一个值?
┌─────────────────────────────────────────────────────────────────────┐
│ __forwarding 指针的妙用 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 【Copy 前 - 栈上】 │
│ │
│ 栈区 │
│ ┌──────────────────────────────────┐ │
│ │ __Block_byref_age_0 │ │
│ │ ┌───────────────────────────┐ │ │
│ │ │ __forwarding ───────────┐│ │ │
│ │ │ age = 20 ││ │ │
│ │ └────────────────────────│─┘ │ │
│ │ │ │ │
│ │ └──────┼──→ 指向自己 │
│ └──────────────────────────────────┘ │
│ │
│ ───────────────────────────────────────────────────────────── │
│ │
│ 【Copy 后 - 栈 + 堆】 │
│ │
│ 栈区 堆区 │
│ ┌──────────────────────────┐ ┌──────────────────────────┐ │
│ │ __Block_byref_age_0 │ │ __Block_byref_age_0 │ │
│ │ ┌───────────────────┐ │ │ ┌───────────────────┐ │ │
│ │ │ __forwarding ───┼───┼─────┼─→│ __forwarding ───┼───┼──┐ │
│ │ │ age = 20 │ │ │ │ age = 20 │ │ │ │
│ │ └───────────────────┘ │ │ └───────────────────┘ │ │ │
│ └──────────────────────────┘ └──────────────────────────┘ │ │
│ ↑ │ │
│ └─────────────────┘ │
│ (指向自己) │
│ │
│ 关键点: │
│ 1. 栈上的 __forwarding 被修改为指向堆上的结构体 │
│ 2. 堆上的 __forwarding 指向自己 │
│ 3. 无论通过栈还是堆访问,最终都是访问堆上的值 │
│ 4. 保证了数据的一致性 │
│ │
└─────────────────────────────────────────────────────────────────────┘
代码验证:
objc
__block int age = 20;
NSLog(@"栈上地址: %p", &age);
void (^block)(void) = ^{
age = 30;
NSLog(@"Block内地址: %p", &age);
};
// ARC 下赋值给 strong 变量会 copy
void (^heapBlock)(void) = block;
NSLog(@"执行前外部地址: %p", &age);
heapBlock();
NSLog(@"执行后外部地址: %p", &age);
// 输出示例:
// 栈上地址: 0x7ffeefbff538
// 执行前外部地址: 0x600000004038 ← copy 后指向堆
// Block内地址: 0x600000004038
// 执行后外部地址: 0x600000004038
4.4 __block 变量的内存管理
cpp
// Block 的 copy 函数
static void __main_block_copy_0(struct __main_block_impl_0*dst,
struct __main_block_impl_0 *src) {
// BLOCK_FIELD_IS_BYREF 表示这是 __block 变量
_Block_object_assign(&dst->age, src->age, BLOCK_FIELD_IS_BYREF);
}
// Block 的 dispose 函数
static void __main_block_dispose_0(struct __main_block_impl_0 *src) {
_Block_object_dispose(src->age, BLOCK_FIELD_IS_BYREF);
}
// desc 结构体包含这两个函数指针
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0),
__main_block_copy_0,
__main_block_dispose_0
};
4.5 __block 修饰对象类型
objc
// __block 修饰对象
__block NSMutableArray *array = [NSMutableArray array];
void (^block)(void) = ^{
array = [NSMutableArray arrayWithObject:@"new"]; // ✅ 可以修改指针
[array addObject:@"item"];
};
编译后的结构:
cpp
struct __Block_byref_array_0 {
void *__isa;
__Block_byref_array_0 *__forwarding;
int __flags;
int __size;
// ⭐ 额外的内存管理函数
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSMutableArray *array; // 对象指针
};
// copy 函数 - 处理 __block 内部的对象
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40,
*(void * *) ((char*)src + 40),
BLOCK_FIELD_IS_OBJECT);
}
// dispose 函数 - 释放 __block 内部的对象
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40),
BLOCK_FIELD_IS_OBJECT);
}
五、Block 的 Copy 操作详解
5.1 不同类型 Block 的 Copy 行为
| Block 类型 | 原位置 | Copy 后 |
|---|---|---|
__NSGlobalBlock__ |
数据段 | 什么也不做,返回原 Block |
__NSStackBlock__ |
栈区 | 复制到堆区,成为 __NSMallocBlock__ |
__NSMallocBlock__ |
堆区 | 引用计数 +1 |
5.2 Copy 操作源码分析
cpp
// Block 的 copy 实现(简化版)
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
if (!arg) return NULL;
aBlock = (struct Block_layout *)arg;
// 1. 如果已经在堆上,增加引用计数
if (aBlock->flags & BLOCK_NEEDS_FREE) {
latching_incr_int(&aBlock->flags);
return aBlock;
}
// 2. 如果是全局 Block,直接返回
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
// 3. 栈 Block,复制到堆上
else {
// 分配内存
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return NULL;
// 复制数据
memmove(result, aBlock, aBlock->descriptor->size);
// 更新标志位
result->flags &= ~(BLOCK_REFCOUNT_MASK | BLOCK_DEALLOCATING);
result->flags |= BLOCK_NEEDS_FREE | 2; // 初始引用计数为 2
// 更新 isa
result->isa = _NSConcreteMallocBlock;
// 调用 copy helper(处理捕获的变量)
_Block_call_copy_helper(result, aBlock);
return result;
}
}
5.3 Copy 对捕获变量的影响
┌─────────────────────────────────────────────────────────────────────┐
│ Block Copy 对变量的影响 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 变量类型 │ Copy 时的处理 │
│ ─────────────────────┼──────────────────────────────────────── │
│ 基本类型 │ 值已在 Block 结构体中,随 Block 一起复制 │
│ 对象类型 (strong) │ 调用 _Block_object_assign,retain 对象 │
│ 对象类型 (weak) │ 调用 _Block_object_assign,弱引用 │
│ __block 基本类型 │ 复制 __Block_byref 结构体到堆 │
│ __block 对象类型 │ 复制结构体 + retain/weak 内部对象 │
│ │
└─────────────────────────────────────────────────────────────────────┘
代码示例:
objc
// 验证 copy 对对象的影响
NSObject *obj = [[NSObject alloc] init];
NSLog(@"初始引用计数: %ld", CFGetRetainCount((__bridge CFTypeRef)obj));
void (^block)(void) = ^{
NSLog(@"obj = %@", obj);
};
NSLog(@"创建Block后: %ld", CFGetRetainCount((__bridge CFTypeRef)obj));
void (^heapBlock)(void) = [block copy];
NSLog(@"Copy后: %ld", CFGetRetainCount((__bridge CFTypeRef)obj));
// ARC 输出:
// 初始引用计数: 1
// 创建Block后: 3 (ARC 自动 copy,Block 持有)
// Copy后: 3 (已经在堆上,引用计数不变)
六、循环引用问题与解决方案
6.1 循环引用的产生
objc
@interface Person : NSObject
@property (nonatomic, copy) void(^block)(void);
@property (nonatomic, copy) NSString *name;
@end
@implementation Person
- (void)test {
// ⚠️ 循环引用!
self.block = ^{
NSLog(@"%@", self.name); // Block 强引用 self
};
// self -> block -> self
}
- (void)dealloc {
NSLog(@"Person dealloc"); // 永远不会调用
}
@end
循环引用图示:
┌─────────────────────────────────────────────────────────────────────┐
│ 循环引用 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────┐ │
│ │ │ │
│ │ Person 对象 │ │
│ │ ┌──────────────┐ │ │
│ │ │ name │ │ │
│ │ │ block ──────┼───┼─────────┐ │
│ │ └──────────────┘ │ │ │
│ │ ↑ │ │ │
│ └──────────┼───────────┘ │ │
│ │ │ strong │
│ self │ strong ↓ │
│ │ ┌──────────────────┐ │
│ │ │ │ │
│ └───────────┤ Block 对象 │ │
│ │ ┌────────────┐ │ │
│ │ │ self ──────┼──┼──┐ │
│ │ └────────────┘ │ │ │
│ └──────────────────┘ │ │
│ ↑ │ │
│ └──────────┘ │
│ │
│ 结果:Person 和 Block 互相强引用,都无法释放 │
│ │
└─────────────────────────────────────────────────────────────────────┘
6.2 解决方案一:__weak
objc
- (void)testWeakSolution {
__weak typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"%@", weakSelf.name); // Block 弱引用 self
};
}
// 更安全的写法:weak-strong dance
- (void)testWeakStrongDance {
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) return;
// 在 Block 执行期间保证 self 不会被释放
NSLog(@"%@", strongSelf.name);
[strongSelf doSomething];
};
}
__weak 的底层实现:
cpp
// __weak 变量在 Block 中的处理
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *__weak weakSelf; // ⭐ 弱引用
};
// copy 时使用 BLOCK_FIELD_IS_WEAK
static void __main_block_copy_0(struct __main_block_impl_0*dst,
struct __main_block_impl_0 *src) {
_Block_object_assign(&dst->weakSelf, src->weakSelf,
BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK);
}
6.3 解决方案二:__block + 手动置 nil
objc
- (void)testBlockSolution {
__block Person *blockSelf = self;
self.block = ^{
NSLog(@"%@", blockSelf.name);
blockSelf = nil; // ⚠️ Block 必须执行,否则仍然循环引用
};
// 必须调用 Block
self.block();
}
适用场景:需要在 Block 内部对捕获的对象进行修改时。
6.4 解决方案三:使用参数传递
objc
@property (nonatomic, copy) void(^block)(Person *person);
- (void)testParameterSolution {
self.block = ^(Person *person) {
NSLog(@"%@", person.name); // 通过参数访问
};
// 调用时传入 self
self.block(self);
}
6.5 各方案对比
┌─────────────────────────────────────────────────────────────────────┐
│ 循环引用解决方案对比 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 方案 │ 优点 │ 缺点 │
│ ─────────────────┼──────────────────────┼─────────────────────── │
│ __weak │ 自动处理,安全 │ 需要 weak-strong 配合 │
│ __block + nil │ 可以修改捕获变量 │ 必须保证 Block 执行 │
│ 参数传递 │ 无需额外修饰符 │ 改变 Block 签名 │
│ __unsafe_unretained │ 性能略好 │ 不安全,可能野指针 │
│ │
└─────────────────────────────────────────────────────────────────────┘
七、实战案例分析
7.1 案例一:网络请求回调
objc
@interface NetworkManager : NSObject
@property (nonatomic, copy) void(^completionHandler)(id data, NSError *error);
@end
@implementation ViewController
- (void)fetchData {
// ❌ 错误写法:循环引用
self.networkManager.completionHandler = ^(id data, NSError *error) {
[self handleData:data];
[self.tableView reloadData];
};
// ✅ 正确写法
__weak typeof(self) weakSelf = self;
self.networkManager.completionHandler = ^(id data, NSError *error) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) return;
[strongSelf handleData:data];
[strongSelf.tableView reloadData];
};
}
@end
7.2 案例二:Timer 回调
objc
// ❌ 错误写法:Timer 强引用 target,造成循环引用
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(timerFired)
userInfo:nil
repeats:YES];
// ✅ 正确写法1:使用 block API
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
repeats:YES
block:^(NSTimer *timer) {
[weakSelf timerFired];
}];
// ✅ 正确写法2:使用代理对象
@interface TimerProxy : NSObject
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL selector;
@end
@implementation TimerProxy
- (void)timerFired:(NSTimer *)timer {
if ([self.target respondsToSelector:self.selector]) {
[self.target performSelector:self.selector withObject:timer];
}
}
@end
// 使用
TimerProxy *proxy = [[TimerProxy alloc] init];
proxy.target = self;
proxy.selector = @selector(timerFired);
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:proxy
selector:@selector(timerFired:)
userInfo:nil
repeats:YES];
7.3 案例三:Block 嵌套
objc
- (void)nestedBlockExample {
__weak typeof(self) weakSelf = self;
self.outerBlock = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) return;
// 内层 Block 直接使用 strongSelf 即可
// 因为 strongSelf 是局部变量,不会造成循环引用
dispatch_async(dispatch_get_main_queue(), ^{
[strongSelf.tableView reloadData];
});
// 如果内层 Block 也被属性持有,则需要再次 weak
strongSelf.innerBlock = ^{
__strong typeof(weakSelf) innerStrongSelf = weakSelf;
if (!innerStrongSelf) return;
[innerStrongSelf doSomething];
};
};
}
7.4 案例四:使用 __block 修改状态
objc
- (void)blockModifyExample {
__block BOOL finished = NO;
__block NSInteger retryCount = 0;
void (^retryBlock)(void) = ^{
retryCount++;
NSLog(@"尝试次数: %ld", (long)retryCount);
if (retryCount >= 3) {
finished = YES;
NSLog(@"达到最大重试次数");
}
};
while (!finished) {
retryBlock();
if (!finished) {
[NSThread sleepForTimeInterval:1.0];
}
}
NSLog(@"最终重试次数: %ld", (long)retryCount);
}
八、Block 内存管理总结
8.1 核心要点图解
┌─────────────────────────────────────────────────────────────────────┐
│ Block 内存管理核心 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. Block 类型与存储位置 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ GlobalBlock (数据段) ← 不捕获变量 │ │
│ │ StackBlock (栈) ← 捕获变量,未 copy │ │
│ │ MallocBlock (堆) ← copy 后 或 ARC 强引用 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 2. 变量捕获规则 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 局部变量 → 值捕获(不可修改原值) │ │
│ │ static变量 → 指针捕获(可修改) │ │
│ │ 全局变量 → 不捕获(直接访问) │ │
│ │ __block变量 → 包装成结构体,指针捕获(可修改) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 3. __block 原理 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ • 变量被包装成 __Block_byref_xxx 结构体 │ │
│ │ • __forwarding 指针保证栈/堆访问一致性 │ │
│ │ • Block copy 时,__block 变量也会 copy 到堆 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 4. 循环引用 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ • 使用 __weak 打破循环 │ │
│ │ • weak-strong dance 保证执行期间对象存活 │ │
│ │ • 或使用 __block + 手动置 nil │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
8.2 面试常见问题
Q1: Block 的本质是什么?
Block 本质是一个 OC 对象,内部有 isa 指针。它封装了函数调用和函数调用环境(捕获的变量)。
Q2: Block 为什么用 copy 修饰?
为了将 Block 从栈复制到堆,延长其生命周期。MRC 下必须手动 copy,ARC 下用 strong 也可以(系统会自动 copy)。
Q3: __block 的作用?
将变量包装成结构体,使 Block 可以修改捕获的局部变量。
Q4: __forwarding 指针的作用?
保证无论 __block 变量在栈上还是堆上,都能正确访问到同一份数据。
Q5: Block 如何避免循环引用?
- __weak:弱引用,配合 __strong 使用
- __block + 手动置 nil:需要保证 Block 执行
- 参数传递:改变 Block 签名
九、调试技巧
9.1 LLDB 调试命令
bash
# 查看 Block 类型
(lldb) po [block class]
# __NSMallocBlock__
# 查看 Block 的详细信息
(lldb) po block
# <__NSMallocBlock__: 0x600000258120>
# 打印 Block 的内存布局
(lldb) memory read --size 8 --count 5 block
# 0x600000258120: 0x00000001a1dca6f8 (isa)
# 0x600000258128: 0x00000000c2000002 (flags)
# ...
# 查看捕获的变量
(lldb) p *(struct __main_block_impl_0 *)block
9.2 检测循环引用
objc
// 在 dealloc 中添加日志
- (void)dealloc {
NSLog(@"%@ dealloc", NSStringFromClass([self class]));
}
// 使用 Debug Memory Graph
// Xcode -> Debug -> View Debugging -> Debug Memory Graph
// 使用 Instruments - Leaks
// Product -> Profile -> Leaks
总结
本文深入剖析了 iOS Block 的底层实现:
- Block 本质:一个包含函数指针和捕获变量的结构体对象
- 三种类型:GlobalBlock、StackBlock、MallocBlock
- 变量捕获:局部变量值捕获、对象指针捕获、static 指针捕获、全局变量不捕获
- __block 原理 :包装成
__Block_byref结构体,通过__forwarding保证访问一致性 - 循环引用 :使用
__weak、__block + nil、参数传递等方式解决
理解这些底层原理,能帮助我们写出更安全、更高效的代码,并在面试中从容应对 Block 相关问题。
参考资料
- Apple Blocks Programming Topics
- libclosure 源码
- Clang Block Implementation Spec
- 《Objective-C 高级编程》- Kazuki Sakamoto