Block由浅入深(2):Block是一个对象

2017-12-06  本文已影响0人  LordLamb

苹果的官方文档将Block描述为具有C语言级的语法,同时有运行时特性的对象,参见Blocks Programming Topics Introduction。为什么说Block是一个对象呢,我们可以从Block的内部实现中得到一定的启示。

为了分析Block的内部实现,我们需要将Block语法转化为我们可读的源代码,我们可以使用clang命令的-rewrite-objc选项将Block语法转化为C++代码。

  1. clang是基于LLVM的C语言族编译器,可以编译C、C++、Objective-C/C++等语言。
  2. 命令是clang -rewrite-objc ${source_file}.m,会生成同名的.cpp文件。

我们先从最简单都Block开始:

int main()
{
    void (^blk)(void) = ^{printf("Block\n");};

    blk();

    return 0;
}

使用clang -rewrite-objc转化后的关键代码如下:

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 (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

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

    return 0;
}

因为C++中struct和class有相似的语义,所以转化后的代码struct我们可以全部认为是class。
我们首先看main函数,第一行代码对应着源代码中的第三行,我们再做一些精简:

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

这里首先调用了__main_block_impl_0的构造函数,然后将生成的对象指针传递给了blk变量。
__main_block_impl_0包含了两部分数据:__block_impl是Block的实现部分,__main_block_desc_0是Block的描述。
__main_block_impl_0构造函数中我们可以看到,它第一个参数是一个void类型的指针,void类型的指针类似于Objective-C中的id,理论上可以传递任何数据。它的第二个参数是一个__main_block_desc_0类型的结构体,我们可以找到这个结构体的定义,大概可以得出这个结构体是用来描述这个block的大小的。
我们回到main函数的第一行,这里调用__main_block_impl_0的构造函数时提供的第一个参数是一个名为__main_block_func_0的静态函数指针,而__main_block_func_0的实现正是源代码中Block体的实现。
__main_block_impl_0构造函数的第二个参数是一个名为__main_block_desc_0_DATA的变量,我们可以看到这个变量刚好是描述这个Block对象大小的。

我们接着看main函数,同样我们做一些精简:

__block_impl *blkTmp = (__block_impl *)blk;
blkTmp->FuncPtr(blk);

这里就是一个简单的通过函数指针调用函数,从前面分析得知,被调用的就是__main_block_func_0函数。

这个例子中,我们跳过了两个重要的信息,一个是__block_impl的isa变量,在这个例子中,isa变量被赋值为&_NSConcreteStackBlock,在GNUStep开源代码中,我们可以找到这个变量的定义:

struct objc_class _NSConcreteGlobalBlock;
struct objc_class _NSConcreteStackBlock;
struct objc_class _NSConcreteMallocBlock;

我们知道每一个Objective-C的对象,在内部实现中都有一个isa指针,指向一个struct objc_class的结构表明这个对象是什么类。所以我们可以得到类似的结论:上面的Block是一个_NSConcreteStackBlock对象。

关于_NSConcreteGlobalBlock_NSConcreteMallocBlock对象我们后面再详细解释。

另外一个重要的信息是__main_block_func_0的参数中有一个__cself(我们可以理解为self),这个变量在这个函数中虽然没有使用到,但是这个变量有重要作用,通过它我们可以访问到Block内部的任何变量,我们将在后续的例子中说明。

上一篇 下一篇

猜你喜欢

热点阅读