Block笔记(二)

上一篇我们了解了一下Block的特性,用法,概念等一些Block的基本使用方法,这一篇我们来向更深处挖掘一下,探究一下Block的实现。

Block的实质

上一篇讲到的Block的语法看上去好像很特别,但它实际上是作为极普通的C语言源代码来处理的。通过支持Block的编译器,含有Block语法的源代码转换为一般C语言编译器能够处理的源代码,并作为极为普通的C语言代码被编译

Clang(LLVM编译器)具有将代码转换为我可读源代码的功能。通过“-rewrite-objc”选项就能将含有Block语法的源代码变换为C++的源代码。说是C++,其实也仅是使用了struct结构,其本质是C语言源代码。

clang -rewrite-objc 源代码文件名

下面我们转换Block语法。

int main() {

    void (^block)(void) = ^{
        printf("Block\n");
    };
    
    block();
    
    return 0;
}

这是一个最简单的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("Block\n");
}

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() {
    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;
}

乍一看完全没有看下去的想法对不对,8行的源代码增加到了四十多行,我们将其拆解分成几个部分理解,多看几次相信就会理解其中原理了,首先我们来看最初源代码中的Block语法。

^{
   printf("Block\n");
 };

我们再看它变换后的源代码。

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        printf("Block\n");
}

Block被作为最简单的C语言函数来处理,根据语法所属的函数名(也就是main)和该Block语法在该函数出现的顺序值(此处为0)来给经clang变换的函数命名。

该函数的参数_cself相当于C++实例方法中指向实例自身变量this,或是Objective-C实例方法中指向对象自身的变量self,即参数_cself指向Block值的变量。

struct __main_block_impl_0 *__cself

之前说C++中的thisObjective-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结构体看起来是不是非常简单。第一个成员变量是impl,我们再来看一下其__block_impl结构体的声明。

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

看了这个结构体的成员变量isa,Flags标志,版本升级所需的区域,函数指针,这些我们会在后面详细说明,我们再来看一下__main_block_impl_0结构体的第二个成员变量Desc,以下为其struct __main_block_desc_0结构体声明。

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
}

从其中的成员名称所示,其结构为今后版本升级所需的区域Block的大小

以上就是__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;
  }

构造函数使用了之前我们之前说明的__block_impl结构体以及struct __main_block_desc_0结构体来初始化__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_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);

struct __main_block_impl_0 *block = &tmp;

这样就容易理解了。该源代码将__main_block_impl_0结构体类型的自动变量tmp,即__main_block_impl_0结构体实例指针,赋值给__main_block_impl_0结构体指针类型的变量block

以下这部分代码对应最初源代码。

 void (^block)(void) = ^{
        printf("Block\n");
    };

将Block语法生成的Block赋值给Block类型变量block。它等同于将__main_block_impl_0结构体实例指针赋值给变量block。该源代码中的Block就是__main_block_impl_0结构体类型的自动变量,即__main_block_impl_0结构体实例

下面再来看看__main_block_impl_0结构体实例构造参数

__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);

__main_block_func_0是由Block语法转换的C语言函数指针__main_block_desc_0_DATA是作为静态全局变量初始化的__main_block_desc_0结构体实例指针。以下为__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)};

由此可以看出该源代码使用__main_block_impl_0结构体实例的大小进行初始化。

好了上面我们将整个__main_block_impl_0结构体的内容拆开分解研究了一遍,现在我们将他们组合起来看看,这个__main_block_impl_0是究竟如何进行的初始化。我们将__main_block_impl_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;

__main_block_func_0函数指针被赋值给成员变量FuncPtr,我们再看看使用Block的部分

block();

此部分经过转换的源代码如下:


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

我们再将其简化一下,简化后如下:

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

这就是最简单的使用函数指针调用函数,由Block语法转换的__main_block_func_0函数的指针被赋值在成员变量FuncPtr中,这也说明了__main_block_func_0函数的参数__cself指向Block的值,在调用该函数的源代码中可以看出Block正是作为参数进行了传递。

到这里我们总算摸清楚了Block实现的原理,现在我们再来说说上文跳过的__block_impl结构体,这个结构体的第一个成员变量叫做isa,是不是似曾相识,Objective-C中对象的实质是不是就是一个objc_object的结构体,在Objective-C中由类生成对象,而类本身也是一个对象。

objc_object的结构体中也有一个成员变量叫做isa,而通过这个isa我们可以访问生成objc_object的类objc_class,而objc_class中也有一个isa用于访问其元类。这部分内容大家可以去看我的另一篇文章iOS中的class,SEL,IMP,由此我们便可以知道所谓Block就是Objective-C对象。

__main_block_impl_0结构体就相当于基于objc_object结构体的Objective-C类对象的结构体,__block_impl便相当于objc_class__main_block_impl_0初始化时的isa = &_NSConcreteStackBlock,即_NSConcreteStackBlock相当于objc_class的实例,将Block当作对象处理时,关于该类的信息就放置于_NSConcreteStackBlock中。

以上就是Block的实质,理解起来可能稍微有些复杂,一定要将整体的源码拆解开来分部理解,多看几次,并且理解iOS中类与对象的实质,这对于理解Block也很有帮助。

你可能感兴趣的:(Block笔记(二))