美文网首页
Block底层原理

Block底层原理

作者: qjsxq | 来源:发表于2020-07-12 15:36 被阅读0次

    Block底层数据结构

    声明一个block

            void (^block)(int, int) =  ^(int a , int b){
            };
    

    通过xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp编译成C++代码,简化为下面的代码

    struct __main_block_desc_0 {
        size_t reserved;
        size_t Block_size;
    };
    
    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;
    };
    

    发现block本质也是一个OC对象,它内部也有isa指针
    block是封装了函数调用以及函数调用环境的OC对象。

    block执行执行逻辑

     void (^block)(void) = ^{
                NSLog(@"Hello, World!");
            };
    
            block();
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      // 构造函数(类似于OC的init方法),返回结构体对象
      __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((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_c60393_mi_0);
            }
    
    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;
            // 定义block变量
            void (*block)(void) = &__main_block_impl_0(
                                                       __main_block_func_0,
                                                       &__main_block_desc_0_DATA
                                                       );
    将__main_block_func_0函数地址 通过__main_block_impl_0构造函数赋值给了FuncPtr,__main_block_impl_0封装了block执行的函数
            // 执行block内部的代码
            block->FuncPtr(block);
        }
        return 0;
    }
    

    Block变量捕获

    可分为局部变量(又分为auto变量,和static变量),全局变量

    int age_ = 10;
    static int height_ = 10;
    
    void (^block)(void);
    
    void test()
    {
    //默认是auto:自动变量,离开作用域就销毁
        auto int a = 10;
        static int b = 10;
        block = ^{
                    NSLog(@"age = %d, height = %d, a = %d, b = %d", age_, height_,a,b);
        };
        a = 20;
        b = 20;
        age_ = 20;
        height_ = 20;
    
    //结果age = 20, height = 20, a = 10, b = 20
    }
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            test();
            block();
        }
        return 0;
    }
    

    编译成底层代码为

    int age_ = 10;
    static int height_ = 10;
    
    struct __test_block_impl_0 {
      struct __block_impl impl;
      struct __test_block_desc_0* Desc;
      int a; // 数值
      int *b; // 指针
      __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int _a, int *_b, int flags=0) : a(_a), b(_b) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    void test()
    {
        auto int a = 10;
        static int b = 10;
        block = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, a, &b));
        a = 20;
        b = 20;
        age_ = 20;
        height_ = 20;
    }
    
    // block 执行代码
    static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
      int a = __cself->a; // bound by copy
      int *b = __cself->b; // bound by copy
        
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_l4_53j9g79j49s6l4zzsk30ym7r0000gn_T_main_c4abf1_mi_0, age_, height_,a,(*b));
        }
    
    

    // 可以发现
    1、block 初始化的时候,局部变量 a的值是直接传入到block 内部,然后,&b是传入的指针
    2、全局变量age_,和height_,没有被传入到block内容,使用的时候是直接访问的
    所以age_,height_ 和 b都能修改,a修改失败
    3、这样设计的原因是因为,
    】局部auto变量的作用域就是test方法内,生命周期也是,超过方法就会被释放,但是block底层是在另外一个函数使用a的,所以需要捕获到blcok内部,
    】b是因为通过static修饰,虽然作用域也是test方法,但是生命周期是整个程序,所以在block执行函数里面,只需要捕获指针,然后通过指针访问就可以
    】至于全局变量,作用域是全局的,在__test_block_func_0可以直接访问到,所以不需要捕获

    block的类型以及存储区域

    Snip20200708_6.png

    在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况

    1、block作为函数返回值时

    typedef void (^LQBlock)(void);
    
    LQBlock myblock()
    {
        int a = 10;
        return ^{
            NSLog(@"---------%d",a);
        };
    }
    

    2、将block赋值给__strong指针时

     int age = 10;
            LQBlock block = ^{
                NSLog(@"---------%d", age);
            };
    

    3、block作为Cocoa API中方法名含有usingBlock的方法参数时

     [array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                
            }];
    

    4、block作为GCD API的方法参数时

     dispatch_async(dispatch_queue_t  _Nonnull queue, ^{
                <#code#>
            });
    

    _block

    _block可以用于解决block内部无法修改auto变量值的问题
    _block不能修饰全局变量、静态变量(static)
    编译器会将_block变量包装成一个对象

      __block int age = 10;
            
            MJBlock block = ^{
                age = 20;
                NSLog(@"age is %d", age);
            };
    

    编译成C++

    struct __Block_byref_age_0 {
        void *__isa;
        struct __Block_byref_age_0 *__forwarding;
        int __flags;
        int __size;
        int age;
    };
    
    
    
    struct __main_block_impl_0 {
        struct __block_impl impl;
        struct __main_block_desc_0* Desc;
        struct __Block_byref_age_0 *age; // _block包装成的对象
    };
    

    内部修改age的代码

     __Block_byref_age_0 *age = __cself->age; // bound by ref
    
         (age->__forwarding->age) = 20;
    

    发现修改age是通过先指向forwarding再指向age修改的,原因是,
    block 一开始是在栈上的__forwarding指向栈上的自己,但是当block被拷贝到堆上时堆上的__block结构体内部的__forwarding指向自己,但是栈上的__forwarding 也指向堆上的__block 结构体。当我们修改age时候肯定希望把修改的值放到堆上,所以通过__forwarding第一个age即使是在栈上的,最终也是修改的堆上的age。

    如下图 Snip20200709_8.png

    相关文章

      网友评论

          本文标题:Block底层原理

          本文链接:https://www.haomeiwen.com/subject/ssoaqktx.html