iOS 知识点 - Block 讲解

核心概念

  • 本质:带有 “自动变量捕获功能” 的匿名函数(闭包)。

Block 与其他概念的对比

概念 特点 是否捕获环境
C 函数指针 纯函数地址 ❌否
Block 函数 + 捕获的上下文 ✅是
Objective-C 方法 对象方法,隐含 self 通过 self 间接访问

类继承链

NSObject
  └── NSBlock (私有基类)
        ├── __NSGlobalBlock__  (全局 Block)
        ├── __NSStackBlock__   (栈 Block)
        └── __NSMallocBlock__  (堆 Block)

基本语法

// 完整形式
返回值类型 (^Block名称)(参数类型列表) = ^返回值类型(参数列表) {
    // 函数体
};

int (^sumBlock)(int, int) = ^int (int a, int b) {
    return a + b;
};

变量捕获机制

三种 Block 类型

类型 isa 指向 存储位置 特点
__NSGlobalBlock__ __NSGlobalBlock__(类对象) 数据段(全局静态区) 不捕获外部变量,生命周期=进程
__NSStackBlock__ __NSStackBlock__(类对象) 捕获变量,随栈帧消亡
__NSMallocBlock__ __NSMallocBlock__(类对象) 从栈拷贝来,引用计数管理

ARC 下自动 copy 规则

MRC 下,必须手动将栈 block 拷贝到堆上。

Block_copy(block);
Block_release(block);

ARC 下,编译器会根据 block 的使用场景自动将栈 block 拷贝到堆上。

  • 触发条件
    - 赋值给 __strong 变量,会自动将栈 block 拷贝到堆上。
    - 作为方法返回值,自动 copy
    - 作为 Cocoa API 回调参数 (例如:dispatch_async)。
    - 放入容器(NSArray, NSDictionary

从 🔝 看出,ARC 下大部分 block 都会变成 __NSMallocBlock__,那么在什么情况下,仍然会有 __NSStackBlock__ 呢?

// 局部作用域内,[直接调用] + [捕获外部变量] + [不满足 “触发条件”] 的 block。
void test(void) {
    int x = 0;
    
    // 默认是 __strong(触发条件),所以需要用 __weak 或 __unsafe_unretained 修饰。 
    __weak void (^block)(void) = ^{
        NSLog(@"%d", x);
    };
}

底层实现原理

举例代码:

void test() {
    int a = 10;
    __block int b = 10;

    void (^blk)(void) = ^{
        NSLog(@"a = %d", a);
        b += 1;
    };
    
    blk();
}

编译后的简化结果:

struct __main_block_impl_0 {
    struct __block_impl impl;  // block 基础信息(isa、FuncPtr 等)
    struct __main_block_desc_0* Desc;  // 绑定 impl.descriptor 指针
    
    /// 自动捕获变量
    int a;  // 捕获到的变量
    struct __Block_byref_b *b_byref;  // 捕获到的 __block 变量
};
  • block() 调用实际是 impl.FuncPtr()

__block 修饰符

普通的变量是无法在闭包内部修改的(只读),如果需要修改变量,需要通过 __block 修饰来共享内存。

__block 变量会被包装成一个结构体:

struct __Block_byref_x {
    void *__isa;
    __Block_byref_x *__forwarding;  // 保证 block copy 到堆后指针仍指向最新值
    int __flags;
    int __size;
    int x;   // 实际存储的值

};
  • block 捕获 __block 变量时,并不会直接拷贝变量 a 的值,而是会捕获 __Block_byref_x*指针,无论 block 在栈 or 堆上,访问的都是同一个 __Block_byref_x → 保证修改同步。

  • __forwarding 指针作用

    • block 从栈拷贝到堆时:
      • __Block_byref_x 本身也会被拷贝到堆
      • __forwarding 指针更新为堆上的新地址
    • block 内访问 x 的方式:
    Block -> __Block_byref_x -> forwarding -> x += 1;
    

block 编译结果示例

举例代码:

void test() {
    int a = 10;
    __block int b = 10;

    void (^blk)(void) = ^{
        NSLog(@"a = %d", a);
        b += 1;
    };
    
    blk();
}

编译后全部的展开结果:

////////////////////
/// 0️⃣__block 修饰的 b
////////////////////
struct __Block_byref_b {
    void *__isa;
    struct __Block_byref_b *__forwarding;
    int __flags;
    int __size;
    int b;  // 实际值
};

////////////////////
/// 1️⃣Block 结构体
////////////////////
struct __main_block_impl_0 {
    struct __block_impl impl;  // block 基础信息(isa、FuncPtr 等)
    struct __main_block_desc_0* Desc;  // 绑定 impl.descriptor 指针
    
    /// 自动捕获变量
    int a;  // 捕获到的变量
    struct __Block_byref_b *b_byref;  // 捕获到的 __block 变量
};

////////////////////
/// 2️⃣_block_impl 展开
////////////////////
struct __block_impl {
    void *isa;      // 指向 __NSStackBlock__/__NSMallocBlock__/__NSGlobalBlock__
    int flags;      // 标志位,比如是否是 __block、是否捕获对象
    int reserved;   // 保留字段
    void (*invoke)(void *, ...); // block 的执行函数指针
    struct __block_descriptor *descriptor; // 描述信息(size, copy, dispose)

};

////////////////////
/// 3️⃣__block_descriptor 展开
////////////////////
struct __block_descriptor {
    unsigned long reserved;                      // 保留字段
    unsigned long size;                          // block 总大小(impl + 捕获变量)
    void (*copy_helper)(void *dst, void *src);   // copy 时调用,处理对象 / __block
    void (*dispose_helper)(void *src);           // 释放 block 时调用

};

////////////////////
/// 4️⃣__main_block_desc_0 展开
////////////////////
struct __main_block_desc_0 {
    unsigned long reserved;
    unsigned long size;
    void (*copy_helper)(void *dst, void *src);
    void (*dispose_helper)(void *src);
};

////////////////////
/// 5️⃣block 执行函数伪代码
////////////////////
void __main_block_invoke(struct __main_block_impl_0 *blk) {
    // a 是按值拷贝
    int a = blk->a;
    printf("a = %d\n", a);

    // b 是 __block 变量,通过 forwarding 修改
    struct __Block_byref_b *b = blk->b_byref->forwarding;
    b->b += 1;
}
posted @ 2025-12-04 20:10  齐生  阅读(2)  评论(0)    收藏  举报