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 相关问题。


参考资料

相关推荐
2501_916008891 小时前
uni-app 上架到 App Store 的项目流程,构建、打包与使用开心上架(Appuploader)上传
android·ios·小程序·https·uni-app·iphone·webview
00后程序员张2 小时前
怎么在 iOS 上架 App,从构建端到审核端的全流程协作解析
android·macos·ios·小程序·uni-app·cocoa·iphone
2501_915918412 小时前
iOS 开发者工具全景指南,构建高效开发、调试与性能优化的多工具工作体系
android·ios·性能优化·小程序·uni-app·iphone·webview
wjm0410062 小时前
秋招ios面试 -- 真题篇(一)
开发语言·ios·面试
微:xsooop18 小时前
iOS 上架4.3a 审核4.3a 被拒4.3a 【灾难来袭】
flutter·unity·ios·uniapp
Haha_bj20 小时前
iOS深入理解事件传递及响应
前端·ios·app
在下历飞雨1 天前
Kuikly基础之动画实战:让孤寡青蛙“活”过来
前端·ios·harmonyos
D***t1311 天前
Swift在iOS中的多任务处理
开发语言·ios·swift
2501_915918411 天前
iOS 手机抓包软件怎么选?HTTPS 调试、TCP 数据流分析与多工具组合的完整实践
android·ios·智能手机·小程序·https·uni-app·iphone