Block内容介绍

Block

前前后后看了4、5遍《Objective-C高级编程》的Block模块,对Block相关的内容有一定的理解,为了方便深入理解Block,结合书中的内容按照个人的理解逻辑对Block相关模块进行梳理,对Block的本质和Block截获变量两个模块内容进行深入解析。希望通过阅读本文章能够清楚的了解Block的原理及正确使用Block。

一、Block的本质

1.1 Block结构说明

1.1.1 含Block代码段

下面代码包含设置block并使用。

#include

int main() {

   int a = 10;

   void(^blk)(void) = ^{

       printf("%d", a);

   };

   blk();

   return 0;

}

1.1.2 LLVM编译后的代码

通过clang -rewrite-objc +文件名查看转化后的内容,去除include相关内容。

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("123");

   }

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;

}

1.1.3 代码段介绍

本小节中,将前文所涉及的代码部分一一介绍,以了解编译器是如何将Block编译成C++语言,进而对Block有更深入的理解。

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

  }

};​

1. block名称。__main_block_impl_0表示main函数的第一个block,如果main函数中有多个block编译器会以最后的数字进行逐字增加。

2. __main_block_impl_0的初始化。初始化无自动变量的Block,需要传入block所定义的函数、block的描述文件(block的大小)、及可选项flag。通过函数、flags及系统判定的block类别设置__block_impl结构体。

3. 该代码不存在自动变量所以结构体中没有关于变量相关内容,而block持有变量会在第二章节中详细说明。现阶段只需要知道如果存在变量,在初始化阶段也需要将变量传入并设置相关的值,对应的结构体也会出现变化。

__block_impl结构体:

struct __block_impl {

  void *isa;

  int Flags;

  int Reserved;

  void *FuncPtr;

};

1. __block_impl结构体,可以理解为block结构体的基类,任何block会有__block_impl结构体。

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

block的方法:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

    printf("123");

}

1. 函数名。__main_block_func_0代表main函数中block的第一个函数。命名规则类似block。

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;

}​

1. __main_block_impl_0的初始化通过__main_block_func_0+__main_block_desc_0_DATA。

2. 通过blk.FuncPtr调用传入的方法。

1.1.4 总结

从上一小节中我们可以知道iOS的block通过编译器在编辑阶段将block编译成对应的结构体,将对应函数及参数在block初始化阶段传入,使block持有函数及相关的变量。

1.2 Block本质

通过block的初始化函数及block的结构体会发现在初始化block时,系统会自动填充impl.isa指针的值,在上文的例子中impl.isa = &_ NSConcreteStateBlock。对runtime熟悉的小伙伴应该对isa指针不陌生 实例对象的isa指针指向父类,父类的isa指针指向元类,元类的isa指向NSObject。而block就是实例对象,它的isa指针指向了_NSConcreteStateBlock,而 _NSConcreteStateBlock的isa指向了其元类最终指向了NSObject,所以block的实质就是对象。

1.2.1 block类型

1.1节例子中block类型为_NSConcreteStateBlock,实际上block一个有3种类型:

_NSConcreteStateBlock: block存在栈中。

_NSConcreteMallocBlock: block存在堆中。

_NSConcreteGlobalBlock: block存在在data数据段中。

block的类型由编译器决定,编译器会根据block的作用域分配block类型。block是全局变量设置如下所示,其类型就为_NSConcreteGlobalBlock。

#include

void(^blk)(void) = ^{

   printf("122");

};

int main() {

   blk();

   return 0;

}

_NSConcreteMallocBlock类型的block需要手动对栈类型block进行copy才会修改为堆block,具体堆block的用户及与栈block的关系会在下一节变量的自动截获中有详细的说明。

二、Block截获自动变量

本节将介绍block是如何持有变量,对上面代码进行修改。

#include

int main() {

   int a = 10;

   void(^blk)(void) = ^{

       printf("%d", a);

   };

   blk();

   return 0;

}

2.1 截获自动变量

根据本届开头的代码段进行clang -rewrite-objc+文件名的操作得到跟1.1.2中类似的部分,不同的内容如下所示:

__main_block_impl_0结构体:

struct __main_block_impl_0 {

  struct __block_impl impl;

  struct __main_block_desc_0* Desc;

  int a;

  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {

   impl.isa = &_NSConcreteStackBlock;

   impl.Flags = flags;

   impl.FuncPtr = fp;

   Desc = desc;

  }

};​

1. 结构体包含a。局部变量a会追加成员变量到block的结构体中,未使用的变量不会被追加到结构体中。

2. 初始化函数。初始化函数增加了int参数。

__main_block_func_0方法:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

  int a = __cself->a; // bound by copy

  printf("%d", a);

}

1. 取值。通过__main_block_impl_0结构体对象获取结构体中的值。

2. bound by copy。编译器自带的注释“// bound by copy”表示block对它引用的局部变量做了只读拷贝,也就是说block引用的是局部变量的副本。因此获取的自动变量值无法在block进行修改,且外部改变变量的值也不会改变block中变量的值。

2.2 __block修饰的变量

    2.1节中介绍了block截获局部变量的原理,然而其对引用的局部变量做的是只读拷贝,因此无法修改截获的局部变量值。为了能够修改截获的自动变量,可以使用__block修饰符修饰变量。

2.2.1 __block导致block结构体变化

main函数修改为:

#include

int main() {

   __block int a = 10;

   void(^blk)(void) = ^{

       printf("%d", a);

   };

   blk();

   return 0;

}

通过clang编译,再来看看block结构体。

__main_block_impl_0结构体:

struct __main_block_impl_0 {

  struct __block_impl impl;

  struct __main_block_desc_0* Desc;

  __Block_byref_a_0 *a; // by ref

  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {

   impl.isa = &_NSConcreteStackBlock;

   impl.Flags = flags;

   impl.FuncPtr = fp;

   Desc = desc;

  }

};

1. __Block_byref_a_0。变量a增加了__block修饰符变为__Block_byref_a_0,关于结构体内容之后会介绍。

2. 初始化函数。在设置a的值是将a.forwording赋值给block的成员变量a。思考如此设计的原因。

__Block_byref_a_0 结构体:

struct __Block_byref_a_0 {

  void *__isa;

__Block_byref_a_0 *__forwarding;

int __flags;

int __size;

int a;

};​

1. *__forwarding。指向自己的指针,通过__Block_byref_a_0->__forwarding->a的方式获取a的值。

__main_block_desc_0结构体:

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

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

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

1. __main_block_desc_0。__main_block_desc_0增加了copy和dispose方法。

2. __main_block_copy_0。_Block_object_assign将对象赋值给对象结构体的成员变量中,相当于retain操作。

3. __main_block_dispose_0。_Block_object_dispose释放赋值在结构体的成员变量,相当于release操作。

4. 通过查看main函数,并没有主动调用copy和dispose函数

copy的调用时机:栈上的block复制到堆上。

1. 通过调用block的copy实例方法。

2. block作为返回值。

3. 将block赋值给__strong修饰的block类型的变量。

dispose的调用时机:堆上的block释放。

1. 没有对象持有block调用dispose。

2.2.2 __Block_byref_a_0结构体设计

    本节将介绍__Block_byref_a_0结构体中的 _ _forwarding指向自己的指针设计的意义。通过copy可以变量复制到堆上,正常情况下栈上的变量修改不会对堆上的对象有影响,然而这对于使用 _ _block修饰的变量而言是个灾难,也背离了 _ _block设计的初衷,所以引入 _ _forwarding,在变量复制到堆上其指针指向堆上的地址。

如下图所示:

Block内容介绍_第1张图片

三、更多

3.1 Block的循环引用

在block中使用__strong修饰的对象,当block从栈复制到堆,该对象就会被block所持有,就会造成循环引用。

伪代码:

class V: ViewController {

   var i: Int = 0

   override func viewDidLoad() {

       let block = {

           print("输出\(self.i)")

       }

       block.copy()

       block()

}

   deinit() {

       print("deinit")

   }

}

通过对block的拷贝,堆上block持有self对象,而self又持有block,所以造成了循环引用。使用weak修饰符修饰self,使block不持有self即可。

你可能感兴趣的:(Block内容介绍)