iOS

iOS底层探索之Block(三)——Block的本质

2021-08-31  本文已影响0人  俊而不逊

Block的本质是什么吗?__Block底层又做了什么呢?

在之前的篇博客中,已经介绍了block的类型,也对产生block的循环引用的问题给出了几种解决方法,那么本篇博客将对block的底层原理进行分析。

Block循环引用问题.png

iOS底层探索之Block(一)——初识Block(你知道几种Block呢?)

iOS底层探索之Block(二)——如何解决Block循环引用问题?

1. 通过block底层结构看本质

在分析block的原理之前,我们得看看block的底层结构是什么样的,还是老规矩 clang一下如下代码:

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        int age = 8;
        void(^block)(void)= ^{
            printf("age:%d",age);
        };
        block();

    }
    return 0;
}

使用clang -rewrite-objc main.m -o main.cpp命令之后,可以很清楚的看到底层的代码结构,如下:

block底层结构

从图中看出是有类型的强转,那么我们去掉类型的强转,还原成最简单的结构去看看,如下:

去掉类型强转
去掉类型的强转,可以看出来block是一个__main_block_impl_0函数的调用,里面有三个参数,分是__main_block_func_0&__main_block_desc_0_DATAage block本质
cpp文件里面,可以很明显的看出block是一个定义为__main_block_impl_0的结构体,该结构体继承自__block_impl
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
void(*block)(void)= __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, age));
 block->FuncPtr(block);

2. block捕获外部变量

我都知道block是具有捕获外部变量的能力的,从我们的结构体中可以看到,我们在外部的 int age,在 block的结构体中也有一个一模一样的age,这是为什么呢?

捕获变量,在编译阶段就自动生成了相应的属性变量,来存储外界捕获的值,属于值拷贝。

捕获变量

这里是不能对age进行赋值变更的,因为是值拷贝,在内部和外部会有相同的变量值,编译不过会报错!

编译不过

3. __block 修改外部变量

block内部需要对外界的变量进行赋值,必须使用__block修饰:

__block 修改外部变量
默认情况下,在block中访问的外部变量是写操作不对原变量生效的,但是你可以加上 __block是可以让其写操作生效的,这又是为什么呢?我先去看看加上__block之后的底层结构是怎么样的,如下所示:
__block 之后的底层结构 block通过函数保存
block 定义出来,这里通过 fp的函数来保存对外部变量的操作,我们手动调用block其实就是调用这个函数,也就是图中的FuncPtr,我们不就行调用blockblock是不会去调用这个功能逻辑代码的。FuncPtr的调用,传入的参数是block自身,在文章前面也介绍了 block的结构体是继承自__block_impl,如下:
FuncPtr调用
__block修饰符修饰的变量在编译时是一个栈 block,捕获到了之后,要对其进行变更操作,运行时就会拷贝到堆区
在这里插入图片描述
从打印结果可以得出是属于地址拷贝,在使用该变量时,实际使用的是指针的方式访问,因此在block中改变该变量的值是可以的,因为修改是同一片地址上的值。

还记得之前博客中的举例为什么第二个打印的值为3吗?

举例
在外部被创建好以后引用计数为1,因为objc没有使用__block进行修饰这时是通过值拷贝的方式进行处理+1block捕获了外部变量,最后在运行时会从栈区拷贝到堆区,这样objc的引用计数会再次加1,所以最后objc的引用计数为3

更多内容持续更新

🌹 喜欢就点个赞吧👍🌹

🌹 觉得有收获的,可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我😁🌹

🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹

上一篇下一篇

猜你喜欢

热点阅读