iOS Block的底层原理

2018-04-03  本文已影响0人  专注移动开发

block的实质

说简单点,block就是匿名函数,和C语言的函数相比就有两点不同

  1. 没有函数名
  2. 带有“^”

但是block本质究竟是个什么鬼???

下面我们写一个简单的block

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^block)(void) = ^{
            printf("I am a block");
        };
        
        block();
    }
    return 0;
}

幸运的是,clang为我们提供了把代码转化为可读源代码的功能,我们可以通过“-rewrite-objc”选项就能把我们的代码转化为C++代码,虽然转化后的文件是.cpp,其实本质就是C语言。

clang -rewrite-objc 源代码 

这里简单说一下clang,clang可以带很多参数,比如:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 源代码 -o 目标文件

假如有链接库的话还可以加-framework 等等,具体的大家clang -help就可以了

我们写的这个block可以说是最简单的一个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("I am a 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, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        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;
}

看到以上的代码,相信很多人表情是黑人问号脸,不过不用紧张,这段代码虽然相对于我们的源码变长了很多,我们只要静下心来仔细分析下,其实很简单,首先我们先看block的实现

^{
   printf("I am a block");
 };

变换后的源代码为

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
            printf("I am a block");
        }

这是一个典型的C语言函数了,函数名为__main_block_func_0, 参数为__main_block_impl_0结构体指针,此处函数名的生成是根据block语法所属的函数名(我们这里是写在main函数中了)和该Block语法在该函数出现的顺序(此处是0),函数的参数__cself相当于C++或java中的this,相当于OC中的self,其实__cself即为指向block值的变量,不过我们这里block语法中就一个简单的打印语句,用不到__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(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

因为这段代码里包含了构造函数,看起来还是比较复杂,我们可以先除去构造函数,这样就会变得非常简单,如下

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
};

这样就看起来清爽多了,这个结构体就包含两个成员变量,第一个是impl,类型是

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

第二个成员变量是Desc指针,类型是

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
}

OK,现在我们再来看看struct __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;
  }

现在我们把__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_impl1_0(__main_block_func_0,&__main_block_desc_0_DATA);
struct __main_block_impl_0 * block = &tmp;

这样就很容易理解了,就是一个简单的结构体指针赋值,即把栈上生成的__main_block_impl1_0结构体实例的指针,赋值给__main_block_impl1_0结构题指针类型的变量,上面这部分代码就是对应源码中的

void (^block)(void) = ^{
     printf("I am a block");
};

将Block语法生成的Block赋给Block类型变量block,它等同于将__main_block_impl_0结构体实例的指针赋给block,我们来看下__main_block_impl_0结构体实例的构造参数

__main_block_impl1_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_DATA = { 0, sizeof(struct __main_block_impl_0)};

由此可知,该源代码使用Block,即__main_block_impl_0结构体实例的大小进行初始化,我们再看看__main_block_impl1_0结构体实例是怎么根据这些参数进行初始化的,我们展开__main_block_impl1_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;

我们再来看一下使用block的部分

block();

这部分可变换为以下源码

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

去掉转换部分

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

这就是简单的使用函数指针调用函数了,由Block语法转换的main_block_func_0函数的指针被赋值给成员变量FuncPtr中,到此我们总算是摸清了block的实质,调用时就是一个简单的函数调用,当然,我们只是说了关于block的调用过程,关于block的问题还有很多,如循环引用,截获自动变量值等,后面的文章我会详细讲解。

上一篇下一篇

猜你喜欢

热点阅读