OC - Block 详解

本文详细介绍了Objective-C中的Block,包括Block的使用、底层数据结构、变量捕获机制、类型、copy操作以及循环引用问题。Block是封装了函数调用及调用环境的OC对象,其底层是一个结构体,通过捕获机制处理不同类型的变量。同时,文章讨论了__block修饰符的作用和内存管理,以及如何避免Block产生的循环引用问题。
摘要由CSDN通过智能技术生成

网络配图

1.Block 的使用

Block 是什么?

块,封装了函数调用以及调用环境的 OC 对象,

Block 的声明
// 1.
@property (nonatomic, copy) void(^myBlock1)(void);
// 2.BlockType:类型别名
typedef void(^BlockType)(void);
@property (nonatomic, copy) BlockType myBlock2;
// 3.
    // 返回值类型(^block变量名)(参数1类型,参数2类型,...)
    void(^block)(void);
Block 的定义
    // ^返回值类型(参数1,参数2,...){};
    // 1.无返回值,无参数
    void(^block1)(void) = ^{
   
        
    };
    // 2.无返回值,有参数
    void(^block2)(int) = ^(int a){
   
        
    };
    // 3.有返回值,无参数(不管有没有返回值,定义的返回值类型都可以省略)
    int(^block3)(void) = ^int{
   
        return 3;
    };
    // 以上Block的定义也可以这样写:
    int(^block4)(void) = ^{
   
        return 3;
    };
    // 4.有返回值,有参数
    int(^block5)(int) = ^int(int a){
   
        return 3 * a;
    };
Block 的调用
    // 1.无返回值,无参数
    block1();
    // 2.有返回值,有参数
    int a = block5(2);
使用示例
    int multiplier = 7;
    int (^myBlock)(int) = ^(int num) {
   
        return num * multiplier;
    };
     
    printf("%d", myBlock(3));
    // prints "21"
Block 的 Code Snippets 快捷方式


2.Block的底层数据结构

  • Block 本质上也是一个 OC 对象,它内部也有个isa指针;
  • Block 是封装了函数调用以及调用环境的 OC 对象;
  • Block 的底层数据结构如下图所示:

Block 的底层数据结构.png

通过 Clang 将以下 Block 代码转换为 C++ 代码,来分析 Block 的底层实现。

// Clang
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
// main.m
int main(int argc, const char * argv[]) {
   
    @autoreleasepool {
   
        
        void(^block)(void) = ^{
   
            NSLog(@"调用了block");
        };
        block();
    }
    return 0;
}
  • Block 底层数据结构就是一个__main_block_impl_0结构体对象,其中有__block_impl__main_block_desc_0两个结构体对象成员。
    main:表示 block 所在的函数
    block:表示这个一个 block
    impl:表示实现(implementation)
    0:表示这是该函数中的第一个 block
  • __main_block_func_0结构体封装了 block 里的代码;
  • __block_impl结构体才是真正定义 block 的结构,其中的FuncPtr指针指向__main_block_func_0
  • __main_block_desc_0是 block 的描述对象,存储着 block 的内存大小等;
  • 定义 block 的本质:
    调用__main_block_impl_0()构造函数,并且给它传了两个参数__main_block_func_0&__main_block_desc_0_DATA。拿到函数的返回值,再取返回值的地址&__main_block_impl_0,把这个地址赋值给 block 变量。
  • 调用 block 的本质:
    通过__main_block_impl_0中的__block_impl中的FuncPtr拿到函数地址,直接调用。
// main.cpp
struct __main_block_impl_0 {
   
    struct __block_impl impl;         // block的结构体
    struct __main_block_desc_0* Desc; // block的描述对象,描述block的大小等
    /*  构造函数
     ** 返回值:__main_block_impl_0 结构体
     ** 参数一:__main_block_func_0 结构体
     ** 参数二:__main_block_desc_0 结构体的地址
     ** 参数三:flags 标识位
     */
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
   
        impl.isa = &_NSConcreteStackBlock; //_NSConcreteStackBlock 表示block存在栈上
        impl.Flags = flags; 
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

// __main_block_func_0 封装了block里的代码
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
   
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_58a448_mi_0);
}

struct __block_impl {
   
    void *isa;     // block的类型
    int Flags;     // 标识位
    int Reserved;  // 
    void *FuncPtr; // block的执行函数指针,指向__main_block_func_0
};

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)}; 
 
int main(int argc, const char * argv[]) {
   
    /* @autoreleasepool */ {
    __AtAutoreleasePool __autoreleasepool; 
        /*
          ** void(^block)(void) = ^{
                 NSLog(@"调用了block");
             };
         ** 定义block的本质:
         ** 调用__main_block_impl_0()构造函数
         ** 并且给它传了两个参数 __main_block_func_0 和 &__main_block_desc_0_DATA
         ** __main_block_func_0 封装了block里的代码
         ** 拿到函数的返回值,再取返回值的地址 &__main_block_impl_0,
         ** 把这个地址赋值给 block
         */
        void(*block)(void) = ((void (*)())&__main_block_impl_0(
                                                               (void *)__main_block_func_0,
                                                               &__main_block_desc_0_DATA
                                                              ));
        /*
         ** block();
         ** 调用block的本质:
         ** 通过 __main_block_impl_0 中的 __block_impl 中的 FuncPtr 拿到函数地址,直接调用
         */      
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

3.Block的变量捕获机制

为了保证 block 内部能够正常访问外部的变量,block 有个变量捕获机制。

  • 对于全局变量,不会捕获到 block 内部,访问方式为直接访问
  • 对于 auto 类型的局部变量,会捕获到 block 内部,block 内部会自动生成一个成员变量,用来存储这个变量的值,访问方式为值传递
  • 对于 static 类型的局部变量,会捕获到 block 内部,block 内部会自动生成一个成员变量,用来存储这个变量的地址,访问方式为指针传递
  • 对于对象类型的局部变量,block 会连同它的所有权修饰符一起捕获
    block 变量捕获机制.png

3.1 auto 类型的局部变量

auto 自动变量:我们定义出来的变量,默认都是 auto 类型,只是省略了。

    auto int age = 10

auto 类型的局部变量会捕获到 block 内部,访问方式为值传递

通过 Clang 将以下代码转换为 C++ 代码:

    int age = 10;
    void(^block)(void) = ^{
   
        NSLog(@"%d",age);
    };
    block();
  • __main_block_impl_0对象内部会生成一个相同的age变量;
  • __main_block_impl_0()构造函数多了个参数,用来捕获访问的外面的age变量的,将它赋值给__main_block_impl_0对象内部的age变量。
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; // bound by copy
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_5ed490_mi_0,age);
}

......

    int age = 10;
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

由于是值传递,我们修改外部的age变量的值,不会影响到 block 内部的age变量。

    int age = 10;
    void(^block)(void) = ^{
   
        NSLog(@"%d",age);
    };
    age = 20;
    block();
    // 10

3.2 static 类型的局部变量

static 类型的局部变量会捕获到 block 内部,访问方式为

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值