Block底层原理

2020-07-12  本文已影响0人  qjsxq

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
上一篇下一篇

猜你喜欢

热点阅读