35

iOS源码解析:Block的本质

 5 years ago
source link: http://www.cocoachina.com/ios/20190214/26328.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Block在iOS开发中的用途非常广,今天我们就来一起探索一下Block的底层结构。

1. Block的底层结构

下面是一个没有参数和返回值的简单的Block:

int main(int argc, char * argv[]) {    @autoreleasepool {        
        void (^block)(void) = ^{
            
            NSLog(@"Hello World!");
        };
        
        block();        
        return 0;
    }
}

为了探索Block的底层结构,我们将main.m文件转化为C++的源码、我们打开命令行。cd到包含main.m文件的文件夹,然后输入: clang -rewrite-objc main.m ,这个时候在该文件夹的目录下会生成main.cpp文件。

这个文件非常长,我们直接拉到文件的最下面,找到main函数:

int main(int argc, char * argv[]) {    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
         //定义block
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));         //调用block
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);        return 0;
    }
}

这第一行代码是定义一个block变量,第二行代码是调用block。这两行代码看起来非常复杂。但是我们可以去简化一下,怎么简化呢?

变量前面的()一般是做强制类型转换的,比如在调用block这一行, block 前面有一个()是(__block_impl *),这就是进行了一个强制类型转换,将其转换为一个 _block_impl 类型的结构体指针,那像这样的强制类型转换非常妨碍我们理解代码,我们可以暂时将这些强制类型转换去掉,这样可以帮助我们理解代码。

化简后的代码如下:

//定义blockvoid (*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);//调用blockblock->FuncPtr(block);

这样化简后的代码就要清爽多了。我们一句一句的看,先看第一句:

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

这句代码的意思好像就是调用 _main_block_impl_0 这个函数,给这个函数传进两个参数 _main_block_func_0&_main_block_desc_0_DATA ,然后得到这个函数的返回值,取函数返回值的地址,赋值给block这个指针。

我们在稍微上一点的位置可以找到 _main_block_impl_0 这个结构:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;//构造函数,类似于OC的init方法
  __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这个结构体的结构我们可以command+f在main.cpp文件中搜索得到:

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

_main_block_desc_0结构体的结构在main.cpp文件的最下面可以找到:

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

这是一个C++的结构体。而且在这个结构体内还包含一个函数,这个函数的函数名和结构体名称一致,这在C语言中是没有的,这是C++特有的。

在C++的结构体包含的函数称为结构体的构造函数,它就相当于是OC中的init方法,用来初始化结构体。OC中的init方法返回的是对象本身,C++的结构体中的构造方法返回的也是结构体对象。

那么我们就知道了, __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA); 返回的就是 _main_block_impl_0 这个结构体对象,然后取结构体对象的地址赋值给block指针。换句话说,block指向的就是初始化后的 _main_block_impl_0 结构体对象。

我们再看一下初始化 _main_block_impl_0 结构体传进去的参数:

  • 第一个参数是 _main_block_func_0 ,这个参数的结构在上面一点的位置也能找到:

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

这个函数其实就是把我们Block中要执行的代码封装到这个函数内部了。我们可以看到这个函数内部就一行代码,就是一个NSlog函数,这也就是 NSLog(@"Hello World!"); 这句代码。

把这个函数指针传给 _main_block_impl_0 的构造函数的第一个参数,然后用这个函数指针去初始化 _main_block_impl_0 这个结构体的第一个成员变量 impl 的成员变量 FuncPtr 。也就是说 FuncPtr 这个指针指向 _main_block_func_0 这个函数。

  • 第二个参数是 &_main_block_desc_0_DATA

    我们看一下这个结构:

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

在结构体的构造函数中,0赋值给了reserved, sizeof(struct __main_block_impl_0) 是赋值给了Block_size,可以看出这个结构体存放的是 _main_block_impl_0 这个结构体的信息。在 _main_block_impl_0 的构造函数中我们可以看到, _main_block_desc_0 这个结构体的地址被赋值给了 _main_block_impl_0 的第二个成员变量 Desc 这个结构体指针。也就是说Desc这个结构体指针指向 _main_block_desc_0_DATA 这个结构体。

那么我们总结一下:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK