• iOS Block详细介绍(block实现)


    Block的实现

    数据结构定义 block的数据结构定义如下图

    对应的结构体定义如下:

    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部分组成。

    1.isa指针。所有对象都有该指针,用于实现对象相关的功能。

    2.flags 用于按bit位表示一些block的附加信息。本文后面介绍 block copy 的实现代码可以看到对该变量的使用。

    3.reserved 保留变量

    4.invoke 函数指针 指向具体的block实现的函数调用地址

    5.descriptor 表示该block的附加描述信息 主要是size 大小 以及 copy  和 dispose函数的指针

    6.variables capture 过来的变量,block 能够访问它外部的局部变量,就是因为将这些变量(或变量的地址)复制到了结构体中。

    该数据结构和后面的clang分析出来的结构实际上是一样的,不过仅是结构体的潜逃方式不一样,如下 2 个结构体 SampleA 和 SampleB 在内存上是完全一样的,原因是结构体本身并不带有任何额外的附加信息。

    struct SampleA {
        int a;
        int b;
        int c;
    };
    
    struct SampleB {
        int a;
        struct Part1 {
            int b;
        };
        struct Part2 {
            int c;
        };
    };

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

    1._NSConcreteGlobalBlock 全局的静态Block 不会访问任何外部变量

    2._NSConcreteStackBlock 保存在栈中的Block 当函数返回时会被销毁

    3._NSContreteMallcoBlock 保存在堆中的block 当引用计数为0的时候会被销毁

    下面我们会分别查看他们各自实现方式上的差别

    研究工具:Clang

    为了研究编译器是如何实现block的  我们需要clang clang提供一个命令 可以将OC的源码改写成C语言的 借此可以研究 block 具体的源码实现方式。该命令是

    clang -rewrite-objc block.c

    NSConcreteGlobalBlock 类型的 block 的实现

    我们先使用这样的方法创建一个工程

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            ^{NSLog(@"hello world");}();
        }
        return 0;
    }

    然后在终端输入 clang -rewrite-objc main.m 会生成一个main.cpp文件 打开去掉无关代码 我们就可以看到block的实现原理。

    struct __block_impl {
        void *isa;
        int Flags;
        int Reserved;
        void *FuncPtr;
    };
    
    
    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) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_xm_fwf5hc0d7f3113f_myyxxql40000gn_T_main_9c11bb_mi_0);}
    
    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)};
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))();
        }
        return 0;
    }

    下面我们就具体看一下是如何实现的。__main_block_impl_0就是该block的实现,从中我们可以看出

    1.一个block实际上是一个对象,它主要由一个isa和一个impl和一个descriptor组成。

    2.由于 clang 改写的具体实现方式和 LLVM 不太一样,并且这里没有开启 ARC。所以这里我们看到 isa 指向的还是_NSConcreteStackBlock。但在 LLVM 的实现中,开启 ARC 时,block 应该是 _NSConcreteGlobalBlock 类型

    3.impl是实际上的函数指针 ,本例中,它指向 __main_block_func_0。这里的 impl 相当于之前提到的 invoke 变量,只是 clang 编译器对变量的命名不一样而已。

    4.descriptor 是用于描述当前这个 block 的附加信息的,包括结构体的大小,需要 capture 和 dispose 的变量列表等。结构体大小需要保存是因为,每个 block 因为会 capture 一些变量,这些变量会加到 __main_block_impl_0 这个结构体中,使其体积变大。在该例子中我们还看不到相关 capture 的代码,后面将会看到。

    NSConcreteStackBlock 类型的 block 的实现

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            int a = 100;
            void(^block)(void) = ^{
                NSLog(@"%d",a);
            };
            block();
        }
        return 0;
    }

    用之前提到的 clang 工具,转换后的关键代码如下:

    struct __block_impl {
        void *isa;
        int Flags;
        int Reserved;
        void *FuncPtr;
    };
    
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int a;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int a = __cself->a; // bound by copy
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_xm_fwf5hc0d7f3113f_myyxxql40000gn_T_main_74e24a_mi_0,a);
            }
    
    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)};
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            int a = 100;
            void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        }
        return 0;
    }

    在本例中我们可以看到:

    1.本例中 isa指向_NSConcreteStackBlock,说明这是一个分配在栈上的实例。

    2.main_block_impl_0 中增加了一个变量 a,在block中引用的变量a实际是在申明block时,被复制到 main_block_impl_0 结构体中的那个变量 a 因为这样,我们就能理解,在 block 内部修改变量 a 的内容,不会影响外部的实际变量 a。

    3.main_block_impl_0 中由于增加了一个变量 a,所以结构体的大小变大了,该结构体大小被写在了 main_block_desc_0 中

    我们修改上面的源码 在变量前面增加 __block 关键字:

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            __block int a = 100;
            void(^block)(void) = ^{
                NSLog(@"%d",a);
            };
            block();
        }
        return 0;
    }

    编译后 变成这个样子

    struct __block_impl {
        void *isa;
        int Flags;
        int Reserved;
        void *FuncPtr;
    };
    
    
    struct __Block_byref_a_0 {
      void *__isa;
    __Block_byref_a_0 *__forwarding;
     int __flags;
     int __size;
     int a;
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_a_0 *a; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__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_a_0 *a = __cself->a; // bound by ref
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_xm_fwf5hc0d7f3113f_myyxxql40000gn_T_main_472c9f_mi_0,(a->__forwarding->a));
            }
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
      void (*dispose)(struct __main_block_impl_0*);
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 100};
            void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        }
        return 0;
    }

    从代码中我们可以看到:

    1.源码中增加一个名为 _Block_byref_a_0的结构体,用来保存我们要捕捉并且能修改的变量a

    2.main_block_impl_0 中引用的是 Block_byref_i_0 的结构体指针,这样就可以达到修改外部变量的作用。

    3.__Block_byref_i_0 结构体中带有 isa,说明它也是一个对象。

    4.我们需要负责 Block_byref_i_0 结构体相关的内存管理,所以 main_block_desc_0 中增加了 copy 和 dispose 函数指针,对于在调用前后修改相应变量的引用计数。

    NSConcreteMallocBlock 类型的 block 的实现

    NSConcreteMallocBlock 类型的 block 通常不会在源码中直接出现,因为默认它是当一个 block 被 copy 的时候,才会将这个 block 复制到堆中。

    变量的复制

    对于 block 外的变量引用,block 默认是将其复制到其数据结构中来实现访问的

    对于用 __block 修饰的外部变量引用,block 是复制其引用地址来实现访问的

    以上就是block的实现原理了 在这里我只是把唐巧的关于block的实现原理的博客又抄写并验证了一下。 原文地址在这里。感谢唐巧。

    http://blog.devtang.com/2013/07/28/a-look-inside-blocks/

  • 相关阅读:
    不要盲目使用新技术,说的就是你,asp.net core!
    MySQL5.7官方文档翻译-innodb事务模型与锁
    Springboot+JPA下实现简易爬虫--爬取豆瓣电视剧数据
    volatile型变量语义讲解一 :对所有线程的可见性
    VAE《放肆》如约而至: 递归算法 + Stream函数式编程 + Lambda相遇实现树状结构
    设计模式(4)——单例模式的学习及其六大战将
    微服务分布式电商项目学习笔记(三)---- docker介绍安装以及使用docker安装软件(2020/7/10)
    杭州生鲜配送管理系统_关于猪肉分割分拆与水果分等级包装的库存与成本计算的若干思考与系统界面设计_升鲜宝供应链管理系统_15382353715
    杭州生鲜配送管理系统_升鲜宝_强烈要求蔬东坡软件公司给出书面的道歉。蔬东坡软件公司利用莫须有的事实来诋毁与打击同行,不道德的竞争手段,来获取市场(一)
    杭州生鲜配送管理系统_升鲜宝_强烈要求蔬东坡软件公司给出书面的道歉。记蔬东坡软件公司利用莫须有的事实来诋毁与打击同行,不道德的竞争手段,来争取市场(三)
  • 原文地址:https://www.cnblogs.com/huanying2000/p/6180173.html
Copyright © 2020-2023  润新知