iOS Block 底层结构与变量捕获原理深度解析

文章目录

    • 前言
    • [一、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 如何避免循环引用?

  1. __weak:弱引用,配合 __strong 使用
  2. __block + 手动置 nil:需要保证 Block 执行
  3. 参数传递:改变 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 的底层实现:

  1. Block 本质:一个包含函数指针和捕获变量的结构体对象
  2. 三种类型:GlobalBlock、StackBlock、MallocBlock
  3. 变量捕获:局部变量值捕获、对象指针捕获、static 指针捕获、全局变量不捕获
  4. __block 原理 :包装成 __Block_byref 结构体,通过 __forwarding 保证访问一致性
  5. 循环引用 :使用 __weak__block + nil、参数传递等方式解决

理解这些底层原理,能帮助我们写出更安全、更高效的代码,并在面试中从容应对 Block 相关问题。


参考资料

相关推荐
tangweiguo030519876 小时前
SwiftUI布局完全指南:从入门到精通
ios·swift
T1an-110 小时前
最右IOS岗一面
ios
坏小虎13 小时前
Expo 快速创建 Android/iOS 应用开发指南
android·ios·rn·expo
光影少年14 小时前
Android和iOS原生开发的基础知识对RN开发的重要性,RN打包发布时原生端需要做哪些配置?
android·前端·react native·react.js·ios
北京自在科技14 小时前
Find My 修复定位 BUG,AirTag 安全再升级
ios·findmy·airtag
Digitally15 小时前
如何不用 USB 线将 iPhone 照片传到电脑?
ios·电脑·iphone
Sim14801 天前
iPhone将内置本地大模型,手机端AI实现0 token成本时代来临?
人工智能·ios·智能手机·iphone
Digitally1 天前
如何将 iPad 上的照片传输到 U 盘(4 种解决方案)
ios·ipad
报错小能手1 天前
ios开发方向——swift并发进阶核心 @MainActor 与 DispatchQueue.main 解析
开发语言·ios·swift
LcGero1 天前
Cocos Creator 业务与原生通信详解
android·ios·cocos creator·游戏开发·jsb