Block底层(类型,循环引用,变量传递,__block)

2021-03-05  本文已影响0人  iOS劝退师

block是什么:

Block是匿名函数(属性保存,在任何地方funptr()调用),但是它的本质还是个对象。(简单的从可以用%@打印就可以看出来)。block底层其实就是个结构体。到OC中就是个对象。

Block类型:(分三类)

全局block:
block内部没有访问它之外的auto局部变量,(staic局部变量,全局变量不算)

堆block、栈block:
block定义后存在栈区,如果作用域改变,就会导致block可能被回收,所以当赋值操作时,编译器在block赋值的时候自动copy一份到堆上,来延长block的生命周期

小知识:
ios 内存空间主要分为:堆、栈、全局/静态区、代码区、数据区
1、栈区(stack)(注意oc下声明的block变量会有个默认的__strong,所以即使是auto变量的block,也会变copy到堆上))
由编译器自动分配并释放,存放函数的参数值,局部变量等。栈是系统数据结构,对应线程/进程是唯一的。优点是快速高效,缺点时有限制,数据不灵活。[先进后出]

block循环引用问题

看这里https://www.jianshu.com/p/846148cd3e1a

__block

为什么局部变量需要用__block?

当然不是所有的局部变量都需要用__block修饰,只有在需要在block内部改变变量值,并且是auto变量,才需要使用__block修饰。

变量类型 捕获到变量内部 传递方式
局部变量auto 捕获 传递值
局部变量static 捕获 传递指针
全局变量 不捕获 直接取值

如表格所示:auto变量在block内部只拿到了值,在block内部捕获到的值的地址和原变量在内存中的地址不是同一个,再怎么修改都无法让原变量产生变化,因为本质上不是同一个变量。
结论在上面,那么我们看看代码:

    int blockTestStr = 123;
    void (^testMyblock)(void) = ^{
        blockTestStr+1;
    };

然后将代码转成c的代码看看:

    int blockTestStr = 123;
    void (*testMyblock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, blockTestStr));

可以发现,block定义中调用了__main_block_impl_0函数,并且将__main_block_impl_0函数的地址赋值给了block。
看下 __main_block_impl_0函数内部结构:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int blockTestStr;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _blockTestStr, int flags=0) : blockTestStr(_blockTestStr) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

再看一下(void *)__main_block_func_0

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int blockTestStr = __cself->blockTestStr; // bound by copy


        blockTestStr+1;
    }

很熟悉,就是我们block块中的代码,所以__main_block_func_0函数中其实存储着我们block中写下的代码。而__main_block_impl_0函数中传入的是(void *)__main_block_func_0,也就说将我们写在block块中的代码封装成__main_block_func_0函数,并将__main_block_func_0函数的地址传入了__main_block_impl_0的构造函数中保存在结构体内。

&__main_block_desc_0_DATA是干什么呢

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)};

__main_block_desc_0中存储着两个参数,reservedBlock_size,并且reserved赋值为0而Block_size则存储着__main_block_impl_0的占用空间大小。最终将__main_block_desc_0结构体的地址传入__main_block_func_0中赋值给Desc。

看完源码应该就知道为什么定义block之后修改局部变量blockTestStr的值,在block调用的时候无法生效。
原因就是(void *)__main_block_func_0中保存到block内部blockTestStr并不是原来的 blockTestStr
看源码

int blockTestStr = __cself->blockTestStr; // bound by copy

看转换的时候自动生成的注释就知道,自动变量val虽然被捕获进来了,但是是用 __cself->val来访问的。Block仅仅捕获了val的值,并没有捕获val的内存地址。因此在block内部怎么改自动变量都没用,因为block内部修改的是变量的副本。

static修饰的静态局部变量
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *blockTestStr = __cself->blockTestStr; // bound by copy

        (*blockTestStr)+1;
    }

明显可以看到捕获的是地址。

全局变量
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) {

        blockTestStr+1;
    }

可以看到结构体里面没有定义blockTestStr成员变量,代码块里也是直接调用blockTestStr。因为全局变量无论在哪里都可以访问。

言归正传那为什么__block可以让auto变量可以在block内部被修改呢,直接看代码

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_blockTestStr_0 *blockTestStr; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_blockTestStr_0 *_blockTestStr, int flags=0) : blockTestStr(_blockTestStr->__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_blockTestStr_0 *blockTestStr = __cself->blockTestStr; // bound by ref

        (blockTestStr->__forwarding->blockTestStr)+1;
    }

可以看到结构体里面多了一段代码,注释by ref清楚明白的告诉我们,传地址。没错,__block修饰之后,传的不再是值,而是地址。

个人理解,有问题请指正

上一篇下一篇

猜你喜欢

热点阅读