美文网首页
iOS Block的底层原理

iOS Block的底层原理

作者: 专注移动开发 | 来源:发表于2018-04-03 23:17 被阅读0次

    block的实质

    说简单点,block就是匿名函数,和C语言的函数相比就有两点不同

    1. 没有函数名
    2. 带有“^”

    但是block本质究竟是个什么鬼???

    下面我们写一个简单的block

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            void (^block)(void) = ^{
                printf("I am a block");
            };
            
            block();
        }
        return 0;
    }
    
    

    幸运的是,clang为我们提供了把代码转化为可读源代码的功能,我们可以通过“-rewrite-objc”选项就能把我们的代码转化为C++代码,虽然转化后的文件是.cpp,其实本质就是C语言。

    clang -rewrite-objc 源代码 
    

    这里简单说一下clang,clang可以带很多参数,比如:

    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 源代码 -o 目标文件
    

    假如有链接库的话还可以加-framework 等等,具体的大家clang -help就可以了

    我们写的这个block可以说是最简单的一个block了,它没有参数没有返回值。

    该源代码通过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;
      __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) {
                printf("I am a block");
            }
    
    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 (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        }
        return 0;
    }
    

    看到以上的代码,相信很多人表情是黑人问号脸,不过不用紧张,这段代码虽然相对于我们的源码变长了很多,我们只要静下心来仔细分析下,其实很简单,首先我们先看block的实现

    ^{
       printf("I am a block");
     };
    

    变换后的源代码为

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
                printf("I am a block");
            }
    

    这是一个典型的C语言函数了,函数名为__main_block_func_0, 参数为__main_block_impl_0结构体指针,此处函数名的生成是根据block语法所属的函数名(我们这里是写在main函数中了)和该Block语法在该函数出现的顺序(此处是0),函数的参数__cself相当于C++或java中的this,相当于OC中的self,其实__cself即为指向block值的变量,不过我们这里block语法中就一个简单的打印语句,用不到__cself,后面我会介绍使用__cself的例子。

    我们再来分析一下__main_block_impl_0结构体

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

    因为这段代码里包含了构造函数,看起来还是比较复杂,我们可以先除去构造函数,这样就会变得非常简单,如下

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
    };
    

    这样就看起来清爽多了,这个结构体就包含两个成员变量,第一个是impl,类型是

    struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
    };
    
    

    第二个成员变量是Desc指针,类型是

    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    }
    

    OK,现在我们再来看看struct __main_block_impl_0的构造函数

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

    现在我们把__main_block_impl_0拆分开分析完了,以上就是初始化结构体成员的源代码,我们先不看每个成员什么意思,我们先来看看该构造函数的调用

    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    

    因为强转太多,我们把强转的部分去掉,如下:

    struct __main_block_impl_0 tmp = __main_block_impl1_0(__main_block_func_0,&__main_block_desc_0_DATA);
    struct __main_block_impl_0 * block = &tmp;
    

    这样就很容易理解了,就是一个简单的结构体指针赋值,即把栈上生成的__main_block_impl1_0结构体实例的指针,赋值给__main_block_impl1_0结构题指针类型的变量,上面这部分代码就是对应源码中的

    void (^block)(void) = ^{
         printf("I am a block");
    };
    

    将Block语法生成的Block赋给Block类型变量block,它等同于将__main_block_impl_0结构体实例的指针赋给block,我们来看下__main_block_impl_0结构体实例的构造参数

    __main_block_impl1_0(__main_block_func_0,&__main_block_desc_0_DATA);
    

    第一个参数是由Block语法转换的C语言函数指针,第二个参数是作为静态全局变量初始化的__main_block_desc_0的结构体实例指针,__main_block_desc_0的初始化部分如下:

    static struct __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    

    由此可知,该源代码使用Block,即__main_block_impl_0结构体实例的大小进行初始化,我们再看看__main_block_impl1_0结构体实例是怎么根据这些参数进行初始化的,我们展开__main_block_impl1_0结构体中的__block_impl结构体,可记述为如下形式:

    struct __main_block_impl_0 {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
    struct __main_block_desc_0 * Desc;
    };
    

    该结构体构造函数会这样初始化

    isa = &_NSConcreteStackBlock;
    Flags = 0;
    Reserved = 0;
    FuncPtr = __main_block_func_0;
    Desc = &__main_block_desc_0_DATA;
    

    我们再来看一下使用block的部分

    block();
    

    这部分可变换为以下源码

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    

    去掉转换部分

    (*block->impl.FuncPtr)(block);
    

    这就是简单的使用函数指针调用函数了,由Block语法转换的main_block_func_0函数的指针被赋值给成员变量FuncPtr中,到此我们总算是摸清了block的实质,调用时就是一个简单的函数调用,当然,我们只是说了关于block的调用过程,关于block的问题还有很多,如循环引用,截获自动变量值等,后面的文章我会详细讲解。

    相关文章

      网友评论

          本文标题:iOS Block的底层原理

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