Skip to content
ShenYj edited this page Mar 5, 2022 · 1 revision

block

.

block的类型

block 主要有三种类型:全局block堆区block栈区block

block的结构

OC对象的底层是由 C++ 结构体支撑的,block 也不例外

NSGlobalBlock

  • 探索代码

    
    #import <Foundation/Foundation.h>
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            NSLog(@"Hello, World!");
            
            void (^block)(void) = ^(){
                NSLog(@"___func____");
            };
            
            block();
        }
        return 0;
    }
    
  • 编译成 C++ 代码

    xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o global-block-main-arm64.cpp

    block-main-arm64.cpp

  • C++ 代码中提取出关键部分

    struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
    };
    
    struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    __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;
    }
    };
    
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_hz_rdy2pt0x3yn9d2qf1qng0wk40000gn_T_main_b2997c_mi_1);
            }
    
    static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_hz_rdy2pt0x3yn9d2qf1qng0wk40000gn_T_main_b2997c_mi_0);
    
            void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        }
        return 0;
    }

通过 main 函数中对 block 的定义和执行可以发现

  1. 初始化的时候,传递了两个参数

    • 函数地址 -> 对应我们 block 代码块部分,被包装成一个 static void __main_block_func_0(struct __main_block_impl_0 *__cself) 函数
      • 并且都会将 block 这个结构体作为第一个参数传递
    • 也是一个结构体,类型是 static struct __main_block_desc_0
      • 这个结构体中包含两个成员,第一个是保留字段,第二个记录了当前我们定义的 block 的大小

    __main_block_impl_0 的构造函数实则 3个 参数,最后一个默认值 0

  2. 当前我声明的这个全局 block 由一个 __main_block_impl_0struct 来呈现, 包含两个成员

    • impl,也是个结构体类型 __block_impl
      • FuncPtr -> 对应的就是 block 代码块的函数地址,构造函数中也是这么来接收的
      • 通过 isa 这个成员定义来看,结构有点和 OC 对象结构相仿
    • Desc,也是个结构体类型 __main_block_desc_0
  3. 调用执行 block

    我是没有系统性的学习过 C++ 的,只有一些 C 基础,看资料讲解,在 C 结构体中不允许定义函数,但是 C++ 允许

    转换成 C++ 这份代码中,执行调用 block 的语句是: ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    • 这里之所以能够将这个全局 block 强转成 __block_impl 类型而直接去通过指针调用 FuncPtr ( 而不是 block->impl->FuncPtr 一层层正常思路调用 )

      • 结构体内存分配

      • 结构体传值

        struct __main_block_impl_0 {
            struct __block_impl impl;
            struct __main_block_desc_0* Desc;
        };

      impl 这个成员刚好排在首位,首元素地址和是结构体地址相同

有参数的 block

  • block 改为有参,同样没有访问外部变量

    int number1 = 20;
    int number2 = 30;
    void (^block)(int, int) = ^(int a, int b){
        NSLog(@"___func____: %d + %d = %d", a, b, a + b);
    };
    
    block(number1, number2);
    
  • C++ 代码

    struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    __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;
    }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_hz_rdy2pt0x3yn9d2qf1qng0wk40000gn_T_main_66a426_mi_0, a, b, a + b);
            }
    
    static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    
    
            int number1 = 20;
            int number2 = 30;
            void (*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    
            ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, number1, number2);
        }
        return 0;
    }

    通过代码可见,block 和上面的结构基本一样,只不过是代码块对应的函数多了两个参数而已,并不会转加给 block 自己的结构体上

NSMallocBlock

刚刚的源码中定义的是一个全局 block,不包含任何外部变量的访问,当我们使用外部变量时,block 又将如何处理

  • Object-C 代码

    #import <Foundation/Foundation.h>
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
    
            int number1 = 20;
            int number2 = 30;
            void (^block)(void) = ^(){
                NSLog(@"___func____: %d + %d = %d", number1, number2, number1 + number2);
            };
            
            block();
        }
        return 0;
    }
    
  • C++ 代码

    malloc-main-arm64.cpp

    struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int number1;
    int number2;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _number1, int _number2, int flags=0) : number1(_number1), number2(_number2) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
    };
    
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int number1 = __cself->number1; // bound by copy
    int number2 = __cself->number2; // bound by copy
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_hz_rdy2pt0x3yn9d2qf1qng0wk40000gn_T_main_ec65bb_mi_0, number1, number2, number1 + number2);
            }
    
    static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    
    
            int number1 = 20;
            int number2 = 30;
            void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, number1, number2));
    
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        }
        return 0;
    }

    通过源码可见,两个外部变量被直接存放在 __main_block_impl_0 结构体内

block捕获变量

.

  • auto: C语言的关键字,用来修饰局部变量,其实我们声明的局部变量默认都会包含,只不过省略了,代表含义就是离开作用域就自动销毁
  • static: 用 static 修饰的局部变量存放在全局数据区,会延长局部变量的寿命,超出函数的生存期
  • 为什么同样是局部变量,捕获方式不同
    虽然访问局部外部变量时, 默认(auto) 和 static 修饰后都会被捕获到 block 的结构体内,但是 static 的生命周期被延长了

    • auto 随着作用域释放,在将来时刻肯定不能去通过内存来访问了
      • 当我们用 __block 修饰时,其实也是针对的 auto 变量, __block 不能用来修饰全局或者 static 变量
    • static 局部变量一段时期内仍在内存中,在将来仍可访问,所以就使用了指针捕获
  • 为何局部变量需要捕获,而全局变量不需要捕获

    C++block 实现的代码层可以了解到,block 定义部分被定义成一个单独的函数,访问的外部变量的真正使用也是在这个函数内,通过 block 自己(函数的第一个参数)访问,由于局部变量作用域的限制,所以需要被捕获,而全局可直接访问,所以不需要捕获

顺着这个思路推理,为什么 self 会被 block 捕获?

OC 方法底层会被转成 C 函数, C 函数默认包含两个参数: self_cmd, 所以 self 是作为形参提供的, 本来也是个指针,肯定是局部变量,所以会被捕获, 并且是指针捕获

当我们捕获的外界变量比较复杂时,比如是一个对象,就会涉及到内存管理,此时 __main_block_desc_0 的结构也会变得复杂
会多两个函数,一个 copy一个 dispose, copy 函数会在 blockcopy 操作的时候自动调用,另外一个用来释放 block 捕获对象的计数

__block对block结构的影响

  • Object-C 代码

    __block int number = 20;
    void (^block)(void) = ^(){
        number = 30;
        NSLog(@"___func____: %d ", number);
    };
    
    block();
    
  • C++ 代码

    struct __Block_byref_number_0 {
    void *__isa;
    __Block_byref_number_0 *__forwarding;
    int __flags;
    int __size;
    int number;
    };
    
    struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    __Block_byref_number_0 *number; // by ref
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_number_0 *_number, int flags=0) : number(_number->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
    };
    
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    __Block_byref_number_0 *number = __cself->number; // bound by ref
    
                (number->__forwarding->number) = 30;
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_hz_rdy2pt0x3yn9d2qf1qng0wk40000gn_T_main_0ef772_mi_0, (number->__forwarding->number));
            }
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->number, (void*)src->number, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->number, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    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};
    
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    
    
            __attribute__((__blocks__(byref))) __Block_byref_number_0 number = {(void*)0,(__Block_byref_number_0 *)&number, 0, sizeof(__Block_byref_number_0), 20};
            void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_number_0 *)&number, 570425344));
    
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        }
        return 0;
    }

与最开始探索 __NSGlobalBlock__ 转成的 C++ 代码,又多了一个结构体 __Block_byref_number_0,也就是将我们需要捕获的目标又包装成了一层结构体(也就是包装成了一个对象), 同时与我们捕获对象时一样, __main_block_desc_0 的结构也会多出两个函数 copydispose

  • __Block_byref_number_0

    struct __Block_byref_number_0 {
        void *__isa;
        __Block_byref_number_0 *__forwarding;
        int __flags;
        int __size;
        int number;
    };
    • __block 修饰变量后,在 block 内部结构中默认对齐保持一份强引用 (仅适用于 ARC 下, 早期 MRC 下情况有所不同 )
    • 对象类型auto 类型局部变量会根据 __strong__weak 决定其内部是强引用还是弱引用
  • __block 修饰的对象

    __attribute__((__blocks__(byref))) __Block_byref_number_0 number = {
            (void*)0,
            (__Block_byref_number_0 *)&number,
            0, 
            sizeof(__Block_byref_number_0), 
            20
        };
    
    • __forwarding 指向自己

最终: 在 block 这个对象中存在一个 __Block_byref_number_0 类型结构体指针 number,在这个结构体中 number 这个成员变量记录了访问的对象

  • 修改外部变量值

    __Block_byref_number_0 *number = __cself->number; // bound by ref
    (number->__forwarding->number) = 30;

    这里在取出 __Block_byref_number_0 *number; 后,是通过 ->__forwarding->number 指针来修改值的(修改的是 block 对象内存空间内的成员变量值)

  • 定义外部变量时

    __attribute__((__blocks__(byref))) __Block_byref_number_0 number = {
        (void*)0,
        (__Block_byref_number_0 *)&number, 
        0, 
        sizeof(__Block_byref_number_0), 
        20
    };

Getting Started

Social

Clone this wiki locally