美文网首页
Block本质的探究

Block本质的探究

作者: 31313_iOS | 来源:发表于2021-02-19 16:25 被阅读0次

    一、准备工作

    • 1、创建一个命令行项目
    • 2、Mac自带的终端Terminal

    进入创建好的项目,并在mian.m里面定义一个Block, 如下所示:

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            //定义一个block
            void (^myBlock)(void) = ^{
                NSLog(@"Hello Block!");
            };
            //block调用
            myBlock();
        }
        return 0;
    }
    

    打开Terminalcd到当前项目main.m所在目录,执行以下指令:

    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
    

    上面的指令目的是借助clang编译main.m得到编译后的文件-main.cpp
    我把编译后的主要的代码贴出来,以便进行后面的探究。

    struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
    };
    #pragma clang assume_nonnull end
    
    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_68_54lgxdz97sj4whg31nyj1d800000gn_T_main_ab6d44_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 (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    
            ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
        }
        return 0;
    }
    

    二、定义Block探究

    由于我们是在main函数里面定义的Block,所以我们在编译后的文件里也是对应从main函数开始探究。
    如果我们把代码对应起来的话就是下面这样的:

    • 定义block,在编译前和编译后
      编译前:
    //定义一个block
     void (^myBlock)(void) = ^{
        NSLog(@"Hello Block!");
    };
    

    编译后:

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

    编译后去除一些强制转换操作后:

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

    经过上面的简化我们不难发现:

      1. 调用了__main_block_impl_0 (参数1, 参数2),并把返回值的地址(&)赋值给了myBlock
    • 2、参数1是: __main_block_func_0
    • 3、参数2是: &__main_block_desc_0_DATA),把参数2的地址值(&)传递进去了.
    1、探究__main_block_impl_0

    所以下一步,我们需要去看看__main_block_impl_0函数是什么?

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
    //构造函数(类似OC的init方法,把外面传进来的参数赋值给自己的成员变量,并返回self),返回结构体对象
      __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 的 c++结构体,里面有:

    • 一个构造函数 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0)
    • 两个主要的成员变量struct __block_impl implstruct __main_block_desc_0* Desc

    所以,我们不难得出,Block其实是一个结构体对象

    从外面传进来的参数赋值给了它的两个成员变量,所以下一步我们需要弄清楚,这两个成员变量是什么。

    2、探究__block_impl

    在编译后的文件中我们可以找到:

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

    __block_impl结构体里面包含的:

    • void *isa, 结构体的地址
    • void *FuncPtr, 函数的地址
    • int Flags,结构体的标识
    • int Reserved,是一个保留字段

    看到这里,我们就不难发现这个Block的内存地址其实就是 __block_impl 的 isa所指向的地址

    为什么这么说?看下面我们贴出的__main_block_impl_0的结构体,这里就不再细说了

    struct __main_block_impl_0 {
      struct __block_impl impl;
     ...
    }
    
    3、探究__main_block_desc_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)};
    

    size_t reserved,保留字段默认值是0
    size_t Block_size,存储了Block的内存大小,通过sizeof(struct __main_block_impl_0)计算出来的
    也就是说,这个结构体主要是来存储Block的描述信息,如:内存大小等

    4、看下_main_block_impl_0(...)构造函数

    现在清楚了Block的两个成员变量后,我们就来看看它的构造函数接收的参数。

    _main_block_impl_0(
          void *fp, 
          struct __main_block_desc_0 *desc, 
          int flags=0
    )
    

    它接收3个参数:

    • 1.void *fp,在函数里面把它赋值给了impl.FuncPtr
    • 2.struct __main_block_desc_0 *desc,在函数里面把它赋值给了Desc.
    • 1.int flags=0,这个是有一个默认值,在上面的调用过程中没有传第三个参数,说明 当前情况下使用默认值就可以。

    再来看下编译后这个构造函数接收的具体参数:

    void (*myBlock)(void) = &__main_block_impl_0(
                               __main_block_func_0,
                              &__main_block_desc_0_DATA));
    
    5、探究__main_block_func_0

    __main_block_func_0是block接收的第一个参数,我们可以在编译后的文件中找到

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_68_54lgxdz97sj4whg31nyj1d800000gn_T_main_ab6d44_mi_0);
    }
    

    __main_block_func_0就是Block封装了执行逻辑的函数,现在我们所看到的内部封装的要执行的函数就是一开始写在Block里面的输出函数:NSLog(@"Hello Block!");

    所以第一个参数:把封装了要执行函数的函数地址传给了__main_block_impl_0, 里面把函数地址赋值给了 impl.FuncPtr = fp;

    6、探究__main_block_desc_0_DATA

    __main_block_desc_0_DATA是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)};
    

    从上面可以看出:

    • __main_block_desc_0_DATA0传给了__main_block_desc_0size_t reserved.
    • __main_block_desc_0_DATAsizeof(struct __main_block_impl_0)传给了__main_block_desc_0size_t Block_size.

    所以第二个参数, 实际上是计算了这个Block的内存大小,并把得到的这个结构体的地址值传递进去。

    到此,结束了定义一个Block的本质的探究。

    三、Block调用的

    首先,我们回看一下调用的代码:

    //block调用
    myBlock();
    

    编译后

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

    编译后去除强制转换就变成如下的代码

     myBlock->FuncPtr(myBlock);
    

    上面的分析我们知道:

    • myBlock__main_block_impl_0构造函数创建完后返回的指针地址
    • myBlock->FuncPtr这句话的作用:
      通过myBlock的地址拿到impl,再通过impl拿到里面的FuncPtr保存的地址值,然后再调用方法
    • 传进去(myBlock)的地址就是传给了封装要执行函数的函数, 即 static void __main_block_func_0(struct __main_block_impl_0 *__cself)

    所以到此也就完成了本次对Block本质的探究!

    四、总结一下

    • 1、Block本质上也是一个OC对象,内部也有一个isa指针。

    • 2、 Block是封装了函数调用和函数调用环境的OC对象

    • 3、Block内部的两个主要成员:

      struct __block_impl impl,保存了Block的内存地址,封装要执行函数的函数地址等
      struct __main_block_desc_0* Desc,内部主要保存了Block的内存大小

    相关文章

      网友评论

          本文标题:Block本质的探究

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