Block本质的探究

一、准备工作

  • 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本质的探究)