美文网首页
Block知识整理

Block知识整理

作者: 水水兔 | 来源:发表于2019-07-11 15:53 被阅读0次

    <font color = 'gray'>2018-10-26 编辑 :yzl </font>

    1. block定义

    用Apple文档的话来说,A block is an anonymous inline collection of code, and sometimes also called a "closure".
    关于闭包,我觉得阮一峰的一句话解释简洁明了:闭包就是能够读取其它函数内部变量的函数。
    这个解释用到block来也很恰当:一个函数里定义了个block,这个block可以访问该函数的内部变量

    2. Block数据结构

    struct Block_descriptor_1 {
        uintptr_t reserved;
        uintptr_t size;
    };
    
    struct Block_layout {
        void *isa;
        volatile int32_t flags; // contains ref count
        int32_t reserved;
        void (*invoke)(void *, ...);
        struct Block_descriptor_1 *descriptor;
        // imported variables
    };
    
    blockStruct.png

    在objc中,根据对象的定义,凡是首地址是*isa的结构体指针,都可以认为是对象(id)。这样在objc中,block实际上就算是对象。

    其结构体成员如下:

    • isa,指向所属类的指针,也就是block的类型
    • flags,标志变量,在实现block的内部操作时会用到
    • Reserved,保留变量
    • FuncPtr,block执行时调用的函数指针

    可以看出,它包含了isa指针(包含isa指针的皆为对象),也就是说block也是一个对象(runtime里面,对象和类都是用结构体表示)。

    <font color = red> 重点理解以下话:</font>
    <font color = #855e42>编译器会根据block捕获的变量,生成具体的结构体定义。block内部的代码将会提取出来,成为一个单独的C函数。创建block时,实际就是在方法中声明一个struct,根据捕获到的变量生成相应的成员变量,并且用捕获到变量的值初始化该struct的成员。而执行block时,就是调用那个单独的C函数,并把该struct指针做为函数参数传递过去。</font>

    int main(int argc, char * argv[]) {
        @autoreleasepool {
            int a = 0;
              void(^block)(void) = ^{
                  int b = a;
                  };
    
                    block();
                    a ++;
    
                    return 0;
                  }
    }
    

    clang (clang -rewrite-objc main.m)之后


    blockclang.png

    自动变量val虽然被捕获进来了,但是是用 __cself->val来访问的。Block仅仅捕获了val的值,并没有捕获val的内存地址。所以在__main_block_func_0这个函数中即使我们重写这个自动变量val的值,依旧没法去改变Block外面自动变量val的值。

    3. Block 类型

    block中的isa指向的是该block的Class。在block runtime中,定义了6种类:

        _NSConcreteStackBlock     栈上创建的block
    
        _NSConcreteMallocBlock    堆上创建的block
    
        _NSConcreteGlobalBlock   作为全局变量的block
    
        _NSConcreteWeakBlockVariable
    
        _NSConcreteAutoBlock
    
        _NSConcreteFinalizingBlock
    
    

    其中我们能接触到的主要是前3种,后三种用于GC不再讨论

    4. __block类型的变量

    默认block捕获到的变量,都是赋值给block的结构体的,相当于const不可改。为了让block能访问并修改外部变量,需要加上__block修饰词。
    加了个__block,被捕获的变量会生成了一个struct(struct __Block_byref)。这个struct的首地址同样为*isa。

    举个例子:
    原代码:

    14719DF5-2C1E-4178-B222-0C9D2C65033E.png

    用clang编译结果


    71A1BB78-95EF-431D-B7CD-E5E131F2D65D.png

    从上面编译结果可以看出,__block修饰的局部变量a,被生成一个·__Block_byref_a_0结构体,并且拷贝一份到堆上,关系图如下:

    1194012-5f5f486bab68191f.jpg

    之后对a操作都是对堆中a值操作,如 (a.__forwarding->a) ++ ,在block 打印a的值,也是(a.__forwarding->a)从而达到了改变a值的目的。

    5. 结束语

    Block不允许修改外部变量的值,这里所说的外部变量的值,指的是栈中指针的内存地址,全局变量,全局静态变量以及静态变量之所以能修改,是因为这些变量都不是在栈上。__block 所起到的作用就是只要观察到该变量被 block所持有,就将“外部变量”在栈中的内存地址放到了堆中。进而在block内部也可以修改外部变量的值。

    待完善,待纠正。。。。。。。。。。

    参考文献

    1. [objc 中的 block](https://blog.icdd bireme.com/2013/11/27/objc-block/#more-41448)
    2. 深入研究Block捕获外部变量和__block实现原理
    3. iOS中__block 关键字的底层实现原理

    相关文章

      网友评论

          本文标题:Block知识整理

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