《Objective-C高级编程》读书笔记--2.3.1--Blocks的实质

前言

Blocks的原理,每当自己对知识体系有一定提升之后,再回过头来看一下曾经读过的书籍,会发现对它的理解逐步加深。借着读书笔记活动,立个小目标,把Block彻底搞明白,重读《Objective-C高级编程 iOS与OS X多线程和内存管理》第二章节block原理部分,一方面给自己做个笔记,另一方面加深以下印象。

block实质

block代码:

void (^blk)(void) = ^ {
        printf("Block");
    };
    blk();
复制代码

执行xcrun -sdk iphonesimulator clang -rewrite-objc 源代码文件名就能将含有Block的代码转换为C++的源代码。我是按照书上的示例,同样转换的main.m文件,转换完之后这里就会多出一个main.cpp的文件,打开很恐怖,六万多行...

实际上和block相关的代码在最后几十行:

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

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, char * argv[]) {

    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;
}
复制代码

这就是我们一直在使用的block,因为都是struct结构看上去有点抽象,不过理解起来并不难。

首先先从__main_block_func_0函数开始,因为我们想要执行的回调看源码都是写在这个函数里面的,block使用的匿名函数(也就是我们定义的block)实际上被作为简单的C语言函数(block__main_block_func_0)来处理,该函数的参数__cself相当于OC实例方法中指向对象自身的变量self,即__self为指向Block值的变量。__self与OC里面的self相同也是一个结构体指针,是__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;
  }
};
复制代码

第一个变量是impl,也是一个结构体,声明如下:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
复制代码

先看FuncPrt,这个就是block括号中函数的函数指针,调用它就能执行block括号中的函数,实际上在调用block的时候就是调用的这个函数指针,执行它指向的具体函数实现。 第二个成员变量是Desc指针,以下为其__main_block_desc_0结构体声明:

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
}
复制代码

其结构为今后版本升级所需的区域和Block的大小。 实际上__main_block_impl_0结构体展开最后就是这样:

struct __main_block_impl_0 {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
  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;
  }
复制代码

这就是整个__main_block_impl_0结构体所包含的,既然定义了这个结构体的初始化函数,那在详细看一下它的初始化过程,实际上该结构体会像下面这样初始化:

isa = &_NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
复制代码

__main_block_func_0这不就是上面说到的那个指向函数实现的那个函数指针,也就是说只需要调用到结构体里面的FuncPtr就能调用到我们的具体实现了。那这个构造函数在哪里初始化的,看上面的源码是在我们定义block的时候:

void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
复制代码

简化为:

struct __mian_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tmp;
复制代码

该源代码将__mian_block_impl_0结构体类型的自动变量,即栈上生成的__mian_block_impl_0结构体实例的指针,赋值给__mian_block_impl_0结构体指针类型的变量blk。听起来有点绕,实际上就是我们最开始定义的blk__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);
复制代码

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

static struct __main_block_desc_0 __main_block_desc_0_DATA = { 
    0, 
    sizeof(struct __main_block_impl_0)
};
复制代码

__main_block_impl_0结构体实例的大小。

接下来看看栈上的__main_block_impl_0结构体实例(即Block)是如何根据这些参数进行初始化的。也就是blk()的具体实现:

((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
复制代码

简化以下:

(*blk->impl.FuncPtr)(blk);
复制代码

FuncPtr正是我们初始化__main_block_desc_0结构体实例时候传进去的函数指针,这里使用这个函数指针调用了这个函数,正如我们刚才所说的,有block语法转换的__main_block_func_0函数的指针被赋值成员变量FuncPtr中。blk也是作为参数进行传递的,也就是最开始讲到的__cself。到此block的初始化和调用过程就结束了。

待更新...

猜你喜欢

转载自juejin.im/post/5c1b97c2e51d4525e00de6c0
今日推荐