美文网首页
Block底层(类型,循环引用,变量传递,__block)

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

作者: iOS劝退师 | 来源:发表于2021-03-05 16:36 被阅读0次

    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修饰之后,传的不再是值,而是地址。

    个人理解,有问题请指正

    相关文章

      网友评论

          本文标题:Block底层(类型,循环引用,变量传递,__block)

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