美文网首页iOS开发资料收集区
iOS Block的内部实现原理

iOS Block的内部实现原理

作者: 阳明先生x | 来源:发表于2018-04-19 13:53 被阅读0次
    image.png

    对应的结构体定义如下:

    struct Block_descriptor {
        unsigned long int reserved;
        unsigned long int size;
        void (*copy)(void *dst, void *src);
        void (*dispose)(void *);
    };
    struct Block_layout {
        void *isa;
        int flags;
        int reserved;
        void (*invoke)(void *, ...);
        struct Block_descriptor *descriptor;
        /* Imported variables. */
    };
    

    通过该图,我们可以知道,一个 block 实例实际上由 6 部分构成:

    isa 指针,所有对象都有该指针,用于实现对象相关的功能。
    flags,用于按 bit 位表示一些 block 的附加信息,本文后面介绍 block copy 的实现代码可以看到对该变量的使用。
    reserved,保留变量。
    invoke,函数指针,指向具体的 block 实现的函数调用地址。
    descriptor, 表示该 block 的附加描述信息,主要是 size 大小,以及 copy 和 dispose 函数的指针。
    variables,capture 过来的变量,block 能够访问它外部的局部变量,就是因为将这些变量(或变量的地址)复制到了结构体中。

    在 Objective-C 语言中,一共有 3 种类型的 block:

    _NSConcreteGlobalBlock 全局的静态 block,不会访问任何外部变量。
    _NSConcreteStackBlock 保存在栈中的 block,当函数返回时会被销毁。
    _NSConcreteMallocBlock 保存在堆中的 block,当引用计数为 0 时会被销毁。

    在给出问题,大家思考下结果吧,如果分别调用以下两个方法,结果如何?

    void blockFunc1()
    {
        int num = 100;
        void (^block)() = ^{
            NSLog(@"num equal %d", num);
        };
        num = 200;
        block();
    }
    
    
    void blockFunc2()
    {
        __block int num = 100;
        void (^block)() = ^{
            NSLog(@"num equal %d", num);
        };
        num = 200;
        block();
    }
    
    

    答案是

    blockFunc1 : num equal 100
    blockFunc2 : num equal 200
    
    

    是不是有人答错了?再来两个函数。这两个的结果与blockFunc2一样,打印出来的 num 为 200

    // 全局变量
    int num = 100;
    void blockFunc3()
    {
        void (^block)() = ^{
            NSLog(@"num equal %d", num);
        };
        num = 200;
        block();
    }
    
    
    void blockFunc4()
    {
        static int num = 100;
        void (^block)() = ^{
            NSLog(@"num equal %d", num);
        };
        num = 200;
        block();
    }
    
    

    疑问:
    我们发现num做为局部变量时加上 _ _block 修饰符、num做为全局变量以及num为静态局部变量时在block中输出结果是一样的,皆为被修改之后的值,而做为局部变量并且未加上__block的num在block中输出的值却还是未赋值之前的值。这是为什么呢?探索这个问题我们就需要看看底层结构是如何实现的了

    探索内部原理

    Objective-C是一个全动态语言,它的一切都是基于runtime实现的!在运行时会将OC转换成C,我们可以利用这个来查看关于block在内部是如何实现的
    新建一个Command Line Tool项目,将以上代码放入main.m中,如图

    image.png

    这里我们打开终端,cd到项目目录下,然后将用下面的命令将OC重写为C

    clang -rewrite-objc main.m
    
    
    image.png

    这时我们可以发现当前目录下多了一个main.cpp文件,打开它并滚到最下面

    image.png image.png

    这里我们可以看到blockFunc1的C语言实现方法

    void blockFunc1()
    {
        int num = 100;
        void (*block)() = ((void (*)())&__blockFunc1_block_impl_0((void *)__blockFunc1_block_func_0, &__blockFunc1_block_desc_0_DATA, num));
        num = 200;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    
    

    去掉类型转换

    void blockFunc1()
    {
        int num = 100;
        // *************************重点句***********************
        void (*block)() = &__blockFunc1_block_impl_0(__blockFunc1_block_func_0, &__blockFunc1_block_desc_0_DATA, num));
        // *****************************************************
        num = 200;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    
    

    这里我们可以看到

    block实际上是指向结构体的指针

    该结构体为

    image.png

    我们来看下带__block的blockFunc2

    image.png

    在 blockFunc1 中,block指向了一个名为__blockFunc1_block_impl_0的结构体,并且在初始化时输入了三个参数(__blockFunc1_block_impl_0最后的flags有默认参数,所以可以不用传参),第三个参数就是我们写的num,与blockFunc2相比较,这里的num并没有带*号,所以说在这里它只是传值而非传址,而下面的【num = 200;】也就没什么卵用了。这就是blockFunc2、blockFunc3与blockFunc4为什么能打印出num改变后的值,而blockFunc1不行的原因。

    image.png

    在这里我们也可以看出:

    编译器会将block的内部代码生成对应的函数

    ** SO **

    我们总结下,block在内部会作为一个指向结构体的指针,当调用block的时候其实就是根据block对�应的指针找到相应的函数,进而进行调用,并传入自身

    __block的实现

    我们再来看看 _ block, _block也被转换成了结构体,并含有5个变量

    struct __Block_byref_num_0 {
      void *__isa;  // isa指针
    __Block_byref_num_0 *__forwarding;  // 实例本身
     int __flags; 
     int __size;
     int num;  // 我们的num值
    };
    
    
    image.png

    图片对应着blockFunc2中的

    __block int num = 100;
    
    

    当创建num并用__block修饰的时候,会初始化这五个变量
    当我们执行

    num = 200;
    
    

    对应着

    (num.__forwarding->num) = 200;
    
    

    上面刚刚提到过 _ _forwarding是实例本身,�即类型结构体__Block_byref_num_0的&num,再找到对应的num变量,将其原来的100修改为200~~

    https://www.jianshu.com/p/ee9756f3d5f6

    相关文章

      网友评论

        本文标题:iOS Block的内部实现原理

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