iOS Block源码解读笔记

用clang -rewrite-objc 文件名生成.cpp文件。顺着一个一个结构体往下查找。

1.先看一个简单block调用

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

main函数原型

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

根据main函数调用,看下__main_block_impl_0、__main_block_func_0、__main_block_desc_0_DATA三个结构体。

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

根据__main_block_impl_0结构体,看下__block_impl、__main_block_desc_0两个结构体。

__block_impl原型

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

__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_func_0原型

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

^{printf("Block\n");};变换后的源代码是__main_block_func_0原型。通过Block使用的匿名函数实际上被当做简单的C语言函数来处理。__main_block_func_0的命名也是通过函数名和block出现的顺序来命名。

__main_block_impl_0如果去掉构造函数,变的非常简单。

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

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

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

第一个成员变量__block_impl:从名称可以联想到某些标志、今后版本升级所需的区域以及函数指针。
第二个成员变量__main_block_desc_0:版本升级所需的区域和Block的大小。

__main_block_impl_0的构造函数
_NSConcreteStackBlock用于初始化__block_impl结构体的isa成员,这也是Block是对象的原因。

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

构造函数的使用

    void (*blk)(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 * blk = &tmp;

该源码将__main_block_impl_0生产的结构体实例指针,赋值给__main_block_impl_0结构体指针类型的变量blk。

使用该Block的部分:blk();
源代码

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

去掉转换后

(*blk-impl->impl.Funcptr)(blk);

简单的使用函数指针调用函数。

总结:
1.impl.isa = &_NSConcreteStackBlock;可重点理解,帮助理解为什么block也是OC对象。
2.__main_block_func_0是函数实现的具体代码
3.__main_block_impl_0 *__cself需重点关注,这和block截获变量和__block使用有莫大关系。
4.只有理解了block的实质,才能更好的使用Block。

2.截获局部变量值

先看看下面截获变量源码。

int main(){
    int val = 10;
    const char * fmt = "val = %d\n";
    void (^blk)(void) = ^{
        printf(fmt,val);
    };

    val = 2;//后面的修改不会产生变化
    fmt = "These values were changed.val = %d\n";

    blk();

    return 0;
}

转换后源代码:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  const char *fmt;
  int val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  const char *fmt = __cself->fmt; // bound by copy
  int val = __cself->val; // bound by copy

        printf(fmt,val);
    }

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 val = 10;
    const char * fmt = "val = %d\n";
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));

    val = 2;
    fmt = "These values were changed.val = %d\n";

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

    return 0;
}

这与前面转码的源代码稍有差异。__main_block_impl_0中多了两个成员变量。
同时需要注意的是:如果Block没有使用的局部变量,是不会被追加的。

__main_block_impl_0构造函数多了两个参数

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val)

构造函数使用:用构造函数对追加的成员变量进行初始化。

    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));

由此可知,在__main_block_impl_0结构体中,局部变量被截获。

__main_block_func_0 实现,即^{printf(fmt,val);}转换后

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  const char *fmt = __cself->fmt; // bound by copy
  int val = __cself->val; // bound by copy

        printf(fmt,val);
    }

这里也充分利用到了__main_block_impl_0 *__cself,即调用__main_block_impl_0的成员变量。

总结:
截获局部变量意味着:Block所使用的局部变量被保存到Block的结构体中。截获自动变量时,将值传递给结构体的构造函数进行保存。

3.__Block

在Block内是无法直接修改外部变量值,但可以使用外部变量。如下,编译器会报错。

int val = 0;
void (^blk)(void) = ^{
    val = 1;
};

解决这两个问题有两种办法。第一种:C语言中有一个变量,允许Block改写值。
1.静态变量
2.静态全局变量
3.全局变量。

int global_val = 1;
static int static_global_val = 2;
int main()
{
    static int static_val = 3;
    void (^blk)(void) = ^{
        global_val *= 1;
        static_global_val *=2;
        static_val *= 3;
    }
}

转换后

int global_val = 1;
static int static_global_val = 2;

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *static_val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *static_val = __cself->static_val; // bound by copy

        global_val *= 1;
        static_global_val *=2;
        (*static_val) *= 3;
    }

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()
{
    static int static_val = 3;
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val));
}

对静态全局变量static_global_val和全局变量global_val的访问与访问前完全相同。对于静态变量static_val稍有不同。看看static_val的使用:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *static_val = __cself->static_val; // bound by copy

        (*static_val) *= 3;
    }

使用静态变量static_val的指针对其进行访问。将静态变量的指针传递给__main_block_impl_0构造函数并保持。这是超出作用于使用变量的最简单方法。

第二种方法使用“__Block说明符”。

int main(){
    __block int val = 10;
    void (^blk)(void) = ^{
        val = 1;
    };
    
    return 0;
}

转换后

struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val_0 *val; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; // bound by ref

        (val->__forwarding->val) = 1;
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(){
    __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));

    return 0;
}

这个__Block变量val是怎样转换过来的呢?

struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};

__Block变量也生成了__Block_byref_val_0结构体类型的自动变量。__Block_byref_val_0在__main_block_impl_0中,这意味着该结构体持有相当于原自动变量的成员变量。
__Block_byref_val_0结构体中最后的成员变量val是相当于原自动变量的成员变量。

__main_block_impl_0转换后成员变量多了__Block_byref_val_0:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val_0 *val; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

^{val = 1;};转换后:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; // bound by ref

        (val->__forwarding->val) = 1;
    }

Block的__main_block_impl_0结构体实例持有指向__Block变量和__Block_byref_val_0结构体实例指针。__Block_byref_val_0结构体实例的成员变量__forwarding持有指向该实例自身的指针。通过成员变量__forwarding访问成员变量val。

__forwarding在栈上和拷贝到堆上有什么区别呢?后面会讨论这个问题。
另外__Block_byref_val_0为什么要单独声明呢?这样做是为了在多个Block中使用__block变量。

int main(){
    __block int val = 10;
    void (^blk0)(void) = ^{
        val = 0;
    };
    
    void (^blk1)(void) = ^{
        val = 1;
    };
    
    return 0;
}

blk0和blk1访问__block变量val。把访问的部分摘出来:

int main(){
    __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
    void (*blk0)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));

    void (*blk1)(void) = ((void (*)())&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA, (__Block_byref_val_0 *)&val, 570425344));

    return 0;
}

去掉强转后:

int main(){
     __Block_byref_val_0 val = {0,&val, 0, sizeof(__Block_byref_val_0), 10};
    blk0 = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &val, 570425344));

    blk1 = &__main_block_impl_1(__main_block_func_1, &__main_block_desc_1_DATA,&val, 570425344));

    return 0;
}

两个Block都是使用了__Block_byref_val_0结构体实例val的指针。这样一来就可以从多个Block中使用同一个__block变量。
同样一个Block中使用多个__block变量,只要增加Block的结构体成员变量与构造函数的参数。

总结:

  1. __block生成时多了__forwarding指针
  2. __forwarding指针指向结构体自身,用于访问val成员变量。当然,这仅是栈上的指针。

你可能感兴趣的:(iOS Block源码解读笔记)