Block 本质

99 阅读35分钟

block的本质是封装了函数调用以及函数调用环境的OC对象。

Block 的实现

最简单的 Block 语法,省略返回值及参数列表。

 int main(int argc, const char * argv[]) {
     
     void(^blk00)(void) = ^{printf("blk00\n");};
     blk00();
     void(^blk01)(void) = ^{printf("blk01\n");};
     blk01();
 ​
     return 0;
 }

通过终端命令: clang -rewrite-objc 源代码文件,将 OC 源代码转换为 C++ 源代码:

 // 系统声明的最基础的 block 结构体,存储 block 的基础信息。
 struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
  };
 ​
 // main 函数中 第 0 个出现的 Block 语法(^{printf("blk00\n");})生成的 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;
     }
 };
 ​
 // 根据 main 函数中 第 0 个出现的 Block 语法生成的函数
 static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
     printf("blk00\n");
 }
 ​
 //main 函数中 第 0 个出现的 Block 语法生成的 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)};
 ​
 struct __main_block_impl_1 {
     struct __block_impl impl;
     struct __main_block_desc_1* Desc;
     __main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int flags=0) {
         impl.isa = &_NSConcreteStackBlock;
         impl.Flags = flags;
         impl.FuncPtr = fp;
         Desc = desc;
     }
 };
 ​
 static void __main_block_func_1(struct __main_block_impl_1 *__cself) {
     printf("blk01\n");
 }
 ​
 static struct __main_block_desc_1 {
     size_t reserved;
     size_t Block_size;
 } __main_block_desc_1_DATA = { 0, sizeof(struct __main_block_impl_1)};
 ​
 // main 函数
 int main(int argc, const char * argv[]) {
     
     // OC源代码: void(^blk00)(void) = ^{printf("blk00\n");}; 
     void(*blk00)(void) = (
         (void (*)()) &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)
     );
     //OC源代码:blk01();  
     ((void (*)(__block_impl *))((__block_impl *)blk00)->FuncPtr)((__block_impl *)blk00);
   
     void(*blk01)(void) = (
         (void (*)())&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA)
     );
     ((void (*)(__block_impl *))((__block_impl *)blk01)->FuncPtr)((__block_impl *)blk01);
      
     return 0;
 }

__main_block_func_0

下面将第 0 个 Block(blk00)的 OC 源代码分成几个部分逐步理解。

首先 OC 源代码中的第0 个Block 语法表达式(匿名函数):

 ^{printf("blk00\n");};

转换为 C++ 源代码得到函数:

 //__cself 相当于 OC 语言实例方法中指向对象自身的 self,即参数 __cself 为指向 Block 结构体的实例
  static void __main_block_func_0(struct __main_block_impl_0 *__cself) { 
      printf("blk00\n"); 
  }

如变换后的源代码所示,通过 Blocks 使用的匿名函数实际上被作为简单的 C 语言函数来处理。函数名,根据 Block 语法所属的函数名(main)和该 Block语法在该函数出现的顺序值(0)来命名。

该函数的参数 __cself 相当于 OC 语言实例方法中指向对象自身的变量self,即参数 __cself为指向 Block值的变量。

struct __main_block_impl_0 *__cself参数说明:参数是__main_block_impl_0( main 函数中第 0 个出现的 Block 的实现)结构体指针。

__main_block_impl_0结构体(省略构造函数)声明如下:

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

__block_impl

该结构体第一个成员变量是系统声明的最基础的 block 结构体,用于存储 block 的基础信息。其结构体的声明为:

 // 系统声明的最基础的 block 结构体,存储 block 的基础信息。
 struct __block_impl {
     void *isa;      // block 内部有 isa 指针,所以说其本质也是OC对象.
     int Flags;      // 标志
     int Reserved;   // 今后版本升级所需的区域
     void *FuncPtr;  // 函数指针
  };

第二个成员变量是 main 函数中第 0 个出现的 Block的描述结构体指针 Desc,__main_block_desc_0结构体声明如下:

 //main 函数中,第 0 个出现的 Block的描述结构体
 static struct __main_block_desc_0 {
     size_t reserved;    // 今后版本升级所需的区域
     size_t Block_size;  // block 的大小
 };

初始化__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_desc_0结构体实例指针;第三参数是 int 型(使用该函数,构造结构体时,第三参数可以省略,默认为 0 )。

用于初始化__block_impl结构体 isa 成员的_NSConcreteStackBlock之后讲解,先看该函数的调用:

 // OC源代码: void(^blk00)(void) = ^{printf("blk00\n");}; 转换如下:
 void(*blk00)(void) = (
     (void (*)())  &__main_block_impl_0( (void *) __main_block_func_0, &__main_block_desc_0_DATA )
 );

可读性较差,将其分解:

 // 获取函数指针
 void *p_func_0 = (void *)__main_block_func_0;
 ​
 // 获取 __main_block_desc_0 结构体实例指针
 struct __main_block_desc_0 *p_desc_0 = &__main_block_desc_0_DATA;
 ​
 // 获取 __main_block_impl_0 结构体实例指针
 struct __main_block_impl_0 *p_blk_impl_0 = &__main_block_impl_0(p_func_0, p_desc_0);
 ​
 void(*blk00)(void);
 blk00 = (void (*)())p_blk_impl_0;
 //以上两行代码等价:void(*blk00)(void) = (void (*)()) p_blk_impl_0;

这样就很容易理解了,再看 OC 源码 Block:

 void(^blk00)(void);
 blk00 = ^{printf("blk00\n");};  // blk00 = (void (*)())p_blk_impl_0;

将 Block 语法生成的 Block (^{printf("blk00\n");)赋值给 Block 类型的变量 blk00。等同于将__main_block_impl_0结构体实例指针赋值给 Block 类型的变量 blk00,所以,该源码中的Block (^{printf("blk00\n");)就是__main_block_impl_0结构体类型的自动变量,即栈内存上生成的__main_block_impl_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_impl_0结构体实例的大小,进行初始化。下面看看栈上的 __main_block_impl_0结构体实例(即 Block)是如何根据这些参数进行初始化的。

展开__main_block_impl_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 部分,转换如下:

 //OC源代码:blk00();  转换:
 ((void (*)(struct __block_impl *))((struct __block_impl *)blk00)->FuncPtr)((struct __block_impl *)blk00);

将其分解:

 // block 结构体实例指针
 struct __block_impl *p_blk00 =  (struct __block_impl *)blk00;
 //函数指针
 void (*p_func)(struct __block_impl *) = (void (*)(struct __block_impl *))(p_blk00->FuncPtrl);
                                                                           
 p_func(p_blk00);

非常清晰看到就是简单的使用函数指针调用函数。正如已经确认的,由 Block 语法得到的__main_block_func_0函数的指针被赋值给成员变量 FuncPtr 。也证明了函数__main_block_func_0的参数struct __main_block_impl_0 *__cself指向 Block 值。Block作为参数进行传递。

Block 是 OC 对象

下面讲解_NSConcreteStackBlock。为了理解它,首先要理解 OC 类和对象的实质。其实,所谓 Block就是 OC 对象。

objc_object这一结构体用于存储 OC 对象。该结构体声明如下:

 struct objc_object {
     Class isa;
 };

再来看看 Class,Class 为objc_class结构体指针类型,该结构体声明如下:

 typedef struct objc_class *Class;
 struct objc_class {
     Class isa;                                          
     // ...
 };

objc_objectobjc_class是每个具体的对象和类的实现中使用的最基本的结构体。下面通过 OC 类来验证一下:

 //  BLKObject.h
 @interface BLKObject : NSObject {
     int val01;
     int val02;
 }
 @end

转换为 C++ 源代码:

 //对象和类实现使用的最基本的结构体
 struct NSObject_IMPL {
     Class isa;
 };
 //BLKObject对象和类实现中使用的结构体
 struct BLKObject_IMPL {
     struct NSObject_IMPL NSObject_IVARS;//对象和类实现使用的最基本的结构体
     int val01;
     int val02;
 };

对比 Block 的结构体:

 // block 实现的最基础的结构体。
 struct __block_impl {
     void *isa;      
     int Flags;      
     int Reserved;   
     void *FuncPtr;  
  };
  //main 中第0个出现的 block 实现中使用的结构体
  struct __main_block_impl_0 {
      struct __block_impl impl;//block 实现使用的最基本的结构体
      struct __main_block_desc_0* Desc;
  };

再展开BLKObject_IMPL结构体的成员NSObject_IMPL结构体并替换 Class 为struct objc_class *:

 struct BLKObject_IMPL {
     struct objc_class *isa;
     int val01;
     int val02;
 };

那么回到刚才的 Block 结构体:

 struct __main_block_impl_0 {
     void *isa;      
     int Flags;      
     int Reserved;   
     void *FuncPtr;  
     struct __main_block_desc_0* Desc;
  };

__main_block_impl_0结构体相当于基于objc_object结构体的 OC 对象的结构体(BLKObject_IMPL)。对其中的成员变量 isa 进行初始化为_NSConcreteStackBlock,即 _NSConcreteStackBlock相当于bjc_class结构体实例。在将 Block作为 OC 的对象处理时,关于该类的信息放置于_NSConcreteStackBlock中。

截获自动变量的值实现

  int main(int argc, const char * argv[]) {
 ​
     int abc = 20; 
     int val = 10;
     const char *fmt = "val = %i\n";
     void(^blk)(void) = ^{printf(fmt, val);};
     val = 2;
     fmt = "更改后: val = %i\n";
     blk();//输出:val = 10
     
     return 0;
 }

转换为 C++ 源码:

 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;
     //被追加到 Block 成员变量与Block 表达式所使用的自动变量类型完全相同,
     //Block 表达式所使用的自动变量的值会被赋值到 Block 的结构体实例的成员中。
     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 argc, const char * argv[]) {
 ​
     int abc = 20;
     int val = 10;
     const char *fmt = "val = %i\n";
     void(*blk)(void) = (
         (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val)
     );
     val = 2;
     fmt = "更改后: val = %i\n";
     ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
     return 0;
 }      

Block 语法表达式(^{printf(fmt, val);};)中使用到的自动变量作为成员变量追加到__main_block_impl_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结构体中的自动变量与自动变量类型完全相同。Block 语法表达式中未使用的自动变量不会追加。Block 自动变量的截获只针对 Block 中使用的自动变量(变量 abc 未截获)。

在初始化结构体实例时,根据传递给构造函数的参数对由自动变量追加的成员变量进行初始化。以下通过构造函数调用确认其参数。

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

将其分解:

 // 获取函数指针
 void *p_func_0 = (void *)__main_block_func_0;
 ​
 // 获取 __main_block_desc_0 结构体实例指针
 struct __main_block_desc_0 *p_desc_0 = &__main_block_desc_0_DATA;
 ​
 // 获取 __main_block_impl_0 结构体实例指针
 struct __main_block_impl_0 *p_blk_impl_0 = & __main_block_impl_0(p_func_0, p_desc_0, fmt, val);
 ​
 void(*blk00)(void) = (void (*)()) p_blk_impl_0;

使用执行 Block 语法时的自动变量 fmt 和 val 来初始化__main_block_impl_0结构体实例。即在该源代码中,__main_block_impl_0结构体实例的初始化如下:

 impl.isa = &_NSConcreteStackBlock;
 impl.Flags = 0;
 impl.FuncPtr = &(__main_block_func_0);
 Desc = &(__main_block_desc_0_DATA);
 fmt = "val = %i\n";
 val = 10;

由此可知,在__main_block_impl_0结构体实例(即 Block)中,自动变量值被截获。下面再来看一下使用 Block 的匿名函数的实现。最初源代码的 Block 语法如下所示:

 ^{printf(fmt, val);}

该源代码可得到以下函数:

 static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
     const char *fmt = __cself->fmt; 
     int val = __cself->val;
     printf(fmt, val);
 }

在 C++的源代码中,将 Block 表达式所使用的自动变量截获到__main_block_impl_0结构体实例的成员上,这些变量在 Block 表达式之前被声明定义。因此,原来的 OC 源代码表达式无需改动便可使用截获的自动变量值执行。所谓截获自动变量值,是在执行 Block 表达式时,Block 表达式所使用的自动变量的值被赋值到 Block 的结构体实例的成员中。

为什么 Block 不能直接使用 C 语言数组类型的自动变量。如前所述,截获自动变量时,将值传递给结构体的构造函数进行赋值。而 C 语言数组不可用数组变量进行赋值。

 //error: array initializer must be an initializer list or string literal
 //数组初始化规则: 
 // 1. 在声明时使用 “{}” 或 “字符串”。
 // 如:char text[4] = {'a', 'b', 'c', \0};或 char text[] = "abc"; 
 // 2. 在声明后逐个初始化
 // 如:text[0] = 'a';
 // ....

__block 修饰符的实现

Block 中所使用的被截获自动变量,仅截获自动变量的值。Block 中使用自动变量后,在 Block 的结构体实例中重写该自动变量也不会改变原先截获的自动变量。以下源代码试图改变 Block 中的自动变量 val。

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

源代码会产生以下编译错误:

 error: variable is not assignable (missing __block type specifier)
 //变量不可赋值(缺少 __block 类型说明符)
 void (^blk)(void)= ^ { val = 1;};
                        ~~~ ^

如前所述,因为在实现上不能改写被截获自动变量的值,所以当编译器在编译过程中检出给被截获自动变量赋值的操作时,便产生编译错误。不过这样一来就无法在 Block 中保存值了,极为不便。

解决这个问题有两种方法

第一种:使用静态或全局变量

具体如下:

1.静态变量。2.静态全局变量。3.全局变量

虽然 Block 语法的匿名函数部分简单地变换为了 C 语言函数,但从这个变换的函数中访问静态全局变量/全局变量并没有任何改变,可直接使用。我们来看看下面这段源代码:

 //全局变量 
 int global_val = 1;
 //静态全局变量
 static int static_global_val = 2;
 ​
 int main(int argc, const char * argv[]) {
     //静态变量
     static int static_val = 3;
     void(^blk)(void) = ^{
         global_val += 1;
         static_global_val += 1;
         static_val += 1;
     };
     blk();
     return 0;
 }

该源代码使用了Block 改写静态变量static_val、静态全局变量static_global_val和全局变量global_val。该 C++源代码如下:

 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;
     //只有static_val 以指针形式追加到 Block 成员变量
     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) {
     // 使用追加到 Block 成员变量static_val的指针
     int *static_val = __cself->static_val; 
     (*static_val) += 1;
     
     //直接取值
     global_val += 1;
     static_global_val += 1;
 }
 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[]) {
     
     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));
 ​
     ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
 ​
     return 0;
 }

对全局变量global_val和静态全局变量static_global_val的访问与转换前相同。不截获全局变量和静态全局变量,直接取值,而静态变量static_val以指针形式截获。 对全局变量global_val和静态全局变量static_global_val的访问与转换前相同。不截获全局变量和静态全局变量,直接取值,而静态变量static_val以指针形式截获。

 struct __main_block_impl_0 {
     struct __block_impl impl;
     struct __main_block_desc_0* Desc;
     int *static_val;
 }
 static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
     int *static_val = __cself->static_val; 
     global_val += 1;
     static_global_val += 1;
     (*static_val) += 1;
 }

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

静态变量的这种方法似乎也适用于自动变量的访问。但是变量作用域结束的同时,原来的自动变量被废弃,因此 Block 中超过变量作用域而存在的变量同静态变量一样,将不能通过指针访问原来的自动变量。实际上,在由 Block 表达式生成的 Block 中 ,可以存有超过其变量作用域的被截获对象的自动变量。这些在下节详细说明。

解决 Block 中不能保存值这一问题的第二种方法是使用__block说明符。更准确的表述方式为__block存储域类说明符(__block storage-class-specifier)。C 语言中有以下存储域类说明符:

typedef / extern / static / auto / register

使用__block 修饰符

__block 说明符类似于 static、auto 和 register 说明符,它们用于指定将变量值存储到哪个存储域中。例如,auto 表示作为自动变量存储在栈中,static 表示作为静态变量存储在数据区中。下面我们来实际使用__block说明符,用它来指定 Block 中想变更值的自动变量。在自动变量声明上追加__block说明符:

 int main(int argc, const char * argv[]) {
     __block int val = 10;
     void(^blk00)(void) = ^{ val = 1; };
     void(^blk01)(void) = ^{ val = 2; };
     return 0;
 }

该源代码转换后如下:

 // __block 捕获的变量结构体
 struct __Block_byref_val_0 {
     void *__isa;
     __Block_byref_val_0 *__forwarding;
     int __flags;
     int __size;
     int val;
 };
 ​
 //Block 结构体
 struct __main_block_impl_0 {
     struct __block_impl impl;
     struct __main_block_desc_0* Desc;
     // __block 捕获的变量结构体
     __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
 };
 ​
 struct __main_block_impl_1 {
     struct __block_impl impl;
     struct __main_block_desc_1* Desc;
     
     __Block_byref_val_0 *val; // by ref
     __main_block_impl_1(void *fp, struct __main_block_desc_1 *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_1(struct __main_block_impl_1 *__cself) {
     __Block_byref_val_0 *val = __cself->val; // bound by ref
     (val->__forwarding->val) = 2;
 }
 ​
 static void __main_block_copy_1(struct __main_block_impl_1*dst, struct __main_block_impl_1*src) {
     _Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);
 }
 ​
 static void __main_block_dispose_1(struct __main_block_impl_1*src) {
     _Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);
 }
 ​
 static struct __main_block_desc_1 {
     size_t reserved;
     size_t Block_size;
     void (*copy)(struct __main_block_impl_1*, struct __main_block_impl_1*);
     void (*dispose)(struct __main_block_impl_1*);
 } __main_block_desc_1_DATA = { 
     0, sizeof(struct __main_block_impl_1), __main_block_copy_1, __main_block_dispose_1
 };
 ​
 int main(int argc, const char * argv[]) {
     
     __Block_byref_val_0 val = {
         (void*)0,
         (__Block_byref_val_0 *)&val, 
         0, 
         sizeof(__Block_byref_val_0), 
         10
     };
     void(*blk00)(void) = ((void (*)())&__main_block_impl_0(
             (void *)__main_block_func_0, 
             &__main_block_desc_0_DATA, 
             (__Block_byref_val_0 *)&val, 
             570425344
         )
     );
     void(*blk01)(void) = ((void (*)())&__main_block_impl_1(
             (void *)__main_block_func_1, 
             &__main_block_desc_1_DATA, 
             ( __Block_byref_val_0 *)&val, 
             570425344
         )
     );
     return 0;
 }

只是在自动变量前附加了__block说明符,源代码就急剧增加。 只是在自动变量前附加了__block说明符,源代码就急剧增加。

 __block int val = 10;

转换为:

  __Block_byref_val_0 val = {
     (void*)0,
     (__Block_byref_val_0 *)&val, 
     0, 
     sizeof(__Block_byref_val_0), 
     10
 };

附加了__block说明符的自动变量 val 转换为__Block_byref_val_0结构体类型自动变量,即栈内存上的__Block_byref_val_0结构体实例。

__Block_byref_val_0结构体声明如下:

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

显然,该结构体的最后一个成员变量 val 是对应源代码中的知自动变量 val;第 0 个 block表达式 ^{ 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 中向静态变量赋值时,使用了指向该静态变量的指针。而向__block 变量赋值要比这个更为复杂。

__main_block_impl_0结构体实例(Block)持有指向__Block_byref_val_0结构体实例(__block 变量)的指针。

__Block_byref_val_0结构体实例的成员变量__forwarding持有指向该实例自身的指针。通过成员变量__forwarding访问成员变量 val. (成员变量 val 是该实例自身持有的变量,它相当于原自动变量)如图所示:

__Block_byref_val_0.png

成员变量__forwarding之后详细说明。

__block 变量的__Block_byref_val_0结构体成员 val 并不是 __main_block_impl_0结构体成员,这样做是为在多个 Block 中使用 __block 变量。如下所示:

 __block int val = 10;
 void(^blk00)(void) = ^{ val = 1; };
 void(^blk01)(void) = ^{ val = 2; };

Block 类型变量都访问了 __block 变量 val ,将转换为 c++ 的源码摘录(再简化)下来:

 __Block_byref_val_0 val = {(void*)0, &val, 0, sizeof(__Block_byref_val_0), 10};
 ​
 blk00 = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &val, 570425344);
 ​
 blk01 = &__main_block_impl_1(__main_block_func_1, &__main_block_desc_1_DATA, &val, 570425344);

两个 Block 都使用了__Block_byref_val_0结构体实例 val 的指针。即可以从多个 Block中使用同一个__block 变量。也一个 Block中使用多个__block 变量。到此大概能够理解__block 变量了。

__main_block_desc_0结构体中增加的成员变量 copy 和 dispose 之后在 2.6 中讲解。

Block 超出变量作用域可存在的原因。在 2.4 中讲解。

_ block变量用结构体成员变量__forwarding存在的原因。在 2.5 中讲解。

Block 存储域

通过前面说明可知,Block 转换为 Block 的结构体类型的自动变量,__block 变量转换为__block 变量的结构体类型的自动变量。所谓结构体类型的自动变量,即栈上生成的该结构体的实例。

另外,通过之前的说明可知 Block 也是 OC 对象。将 Block 当作OC 对象来看时,该Block的类为_NSConcreteStackBlock。虽然该类并没有出现在已变换源代码中,但有很多与之相似的类:

  • __NSStackBlock__,该类的对象存储在栈上。
  • __NSGlobalBlock__,类对象存储在程序的数据区域( .data区)中。
  • __NSMallocBlock__类对象则存储在由 malloc 函数分配的内存块(即堆)中。

NSGlobalBlock

 int val = 10;
 typedef int(^blk_t)(int);
 ​
 blk_t blk_0 = ^(int count){return count;};
 blk_t blk_1 = ^(int count){return val + count;};
 ​
 int main(int argc, const char * argv[]) {
     
     blk_t blk_2 = ^(int count){return count;};
     NSLog(@"%@", blk_0);//打印 __NSGlobalBlock__
     NSLog(@"%@", blk_1);//打印 __NSGlobalBlock__
     NSLog(@"%@", blk_2);//打印 __NSGlobalBlock__
   
     return 0;
 }

以上三个 Block 的类均为 NSGlobalBlock 类。

blk_0blk_1 用 Block 结构体实例存储在程序的数据区域中。因为在使用全局变量的地方不能使用自动变量,所以不存在对自动变量进行截获(blk_1直接访问全局变量)。blk_2也未截获自动变量。

以上三个 Block 结构体实例的内容不依赖于执行时的状态,所以整个程序中只需一个实例。 因此将 Block 结构体实例设置在与全局变量相同的数据区域中即可。只要不截获自动变量,就可以将 Block 存储在程序的数据区域中。

NSStackBlock

只要截获自动变量且未 copy ,Block 是 NSStackBlock。

 int main(int argc, const char * argv[]) {
     typedef int(^blk_t)(int);
     int val = 10;
     blk_t blk_0 = ^(int count){return val + count;};
     NSLog(@"%@", blk_0);
     //在非 ARC 中打印是 NSStackBlock, 但是在 ARC 中就是 NSMallocBlock
     //在 ARC 中,大多数情形下编译器会恰当地进行判断,自动生成将 Block 从栈上复制到堆上的代码,
     //而在非 ARC 中,则需要手动 copy.
     return 0;
 }

blk_0在非 ARC 中是 NSStackBlock, 但是在 ARC 中就是 NSMallocBlock,因为在 ARC 中,大多数情形下编译器会恰当地进行判断,自动将 block 从栈复制到堆上,而在非 ARC 中,则需要手动 copy。

NSMallocBlock

只需要对 NSStackBlock 进行 copy 操作就可以获取存储在堆上的 NSMallocBlock 类 Block (在 ARC 中多数情形下编译器会恰当地进行判断,自动将 block 从栈复制到堆上)。上节遗留问题:

●Block超出变量作用域可存在的原因

_ block变量用其结构体成员变量__forwarding存在的原因

存储在全局变量上的 Block,从变量作用域外也可以通过指针安全地使用。但设置在栈上的 Block,如果其所属的变量作用域结束,该 Block 就被废弃。由于__block变量也配置在栈上,同样地,如果其所属的变量作用域结束,则该__block变量也会被废弃。

Block 提供了将 Block 和__block变量从栈上复制到堆上的方法来解决这个问题。将配置在栈上的 Block 复制到堆上,这样即使Block 语法记述的变量作用域结束,堆上的 Block 还可以继续存在。__block变量结构体成员__forwarding可以实现无论__block变量配置在栈上还是堆上时都能够正确地访问__block变量。

有时在__block修饰的变量存储在堆上,也可以访问栈上的__block变量。只要栈上的结构体实例成员变量__forwarding指向堆上的结构体实例,那么不管是从栈上的__block变量还是从堆上的__block变量都能够正确访问。 实际上当 ARC 有效时,大多数情形下编译器会恰当地进行判断,自动生成将 Block 从栈上复制到堆上的代码。我们来看一下下面这个返回 Block 的函数。

 typedef int(^blk_t)(int);
 blk_t func(int val) {
     return ^(int count){return val + count;};
 }

该源代码为返回配置在栈上的 Block 的函数。即程序执行中从该函数返回函数调用方时变量作用域结束,因此栈上的 Block 也被废弃。虽然有这样的问题,但该源代码通过对应ARC的编译器可转换如下:

 blk_t func(int val) {
     blk_t tmp = &_func_block_impl_0(_func_block_func_0, &_func_block_desc_0_DATA, val);
     tmp = _Block_copy(tmp);
     return objc_autoreleaseReturnValue(tmp);
 }

将通过 Block 语法生成的 Block,即存储在栈上的 Block 结构体实例赋值给 Block 类型的变量 tmp。_Block_copy函数将栈上的 Block 复制到堆上。复制后, 将堆上的地址作为指针赋值给变量 tmp。将堆上的 Block 作为 OC 对象注册到 autoreleasepool中,然后返回该对象。

将 Block作为函数返回值返回时,编译器会自动生成复制到堆上的代码。前面讲到过“大多数情况下编译器会适当地进行判断”,不过在此之外的情况下需要手动生成代码,将 Block 从栈上复制到堆上。此时我们使用 “copy 实例方法"。

编译器在向方法或函数的参数中传递Block时不能进行判断。但是在方法或函数中适当地复制了传递过来的参数(调用 copy 方法),那么就不必在调用该方法或函数前。手动复制了。以下方法或函数不用手动复制。

  • Cocoa框架的方法且方法名中含有usingBlock等时
  • GCD 的API

例如,在使用 NSArray 类的enumerateObjectsUsingBlock实例方法以及dispatch_asyne函数时,不用手动复制。相反地,在NSArray 类的initWithObjects实例方法上传递 Block时需要手动复制。下面我们来看看源代码。

 typedef int(^blk_t)(void);
 ​
 id getBlockArray( int val){
     return [[NSArray alloc] initWithObjects:^{NSLog(@"blk0:%d", val);}, ^{NSLog(@"blk1:%d", val);}, nil];
 }
 int main(int argc, const char * argv[]) {
     
     id obj = getBlockArray();
     blk_t blk = (blk_t)[obj objectAtIndex:0];
     blk();
     return 0;
 }

getBlockArray方法在栈上生成两个 Block,并传递给 NSArray 类的initWithObjects实例方法。在getBlockArray方法调用方,从 NSArray 对 象中取出 Block 并执行。 该源代码在执行时发生异常,应用程序强制结束。这是由于在getBlockArray函数执行结束时,栈上的 Block 被废弃,变成了野指针的缘故。因为编译器在向方法或函数的参数中传递 Block 时不能进行判断是否需要复制。因此在此情形下让编程人员手动进行复制。该源代码像下面这样修改一下即可正常运行。

 id getBlockArray(int val){
     return [[NSArray alloc] initWithObjects:
             [^{NSLog(@"blk0:%d", val);} copy],
             [^{NSLog(@"blk1:%d", val);} copy],
             nil];
 }

按存储 Block 的存储域,将 copy 方法进行复制的动作总结如表所示:

Block的类副本源的存储存储域复制效果
NSStackBlock从栈复制到堆
NSGlobalBlock程序的数据区域什么也不做
NSMallocBlock引用计数增加

不管 Block 存储在何处,用 copy 方法复制都不会引起任何问题。在不确定时调用 copy 方法即可。

但是在 ARC 中不能显式地 release,所以尽量不要对 Block 使用 retain 操作。 因为不管 Block 配置在何处,用 copy 方法复制都不会引起任何问题。在不确定时调用 copy 方法即可。

__block 变量存储域

使用__block变量的 Block 从栈复制到堆上时__block变量也会受到影响。总结如表2-5所示。

__block 变量的存储域Block 从栈复制到堆时的影响
从栈复制到堆并被 Block 持有
被 Block 持有

若在1个 Block 中使用__block变量,则当该 Block 从栈复制到堆时,使用的所有__block 变量也全部被从栈复制到堆。此时,Block 持有__block变量。即使在该 Block 已复制到堆的情形下,复制 Block 也对所使用的__block变量没有任何影响。

在多个 Block中 使用__block变量时,因为最先会将所有的 Block 配置在栈上,所以__block变量也会配置在栈上。在任何一个Block 从栈复制到堆时,__block变量也会一并从栈复制到堆并被该 Block 所持有。当剩下的 Block 从栈复制到堆时,被复制的Block持有__block变量,并增加__block变量的引用计数。

与 OC 的引用计数式内存管理完全相同。使用__block变量的 Block 持有该变量。如果 Block 被废弃,它所持有的__block变量也就被释放。

那么在理解了__block变量的存储域之后,我们再回顾一下 2.4 节中讲过的使用__block变量用结构体成员变量__forwarding的原因。“不管__block变量存储在栈上还是在堆上,都能够正确地访问该变量”。正如这句话所述,当__block变量从栈复制到堆。此时可同时访问栈上的__block变量和堆上的__block变量。源代码如下:

 __block int val = 0;
 void(^blk)(void) = [^{ val = val + 1;} copy];
 val = 10;
 NSLog(@"val = %i",val);// val = 10
 blk();
 NSLog(@"val = %i",val);// val = 11

利用 copy 方法复制使用了__block变量的 Block 语法。Block 和__block变量两者均是从栈复制到堆。此代码中在Block语法的表达式中使用初始化后的__block变量。

在 Block 表达式(^{ val = val + 1;})中,该变量 val 为复制到堆上的__block变量结构体实例,而使用的与 Block 无关的变量(val = 10;) val , 为复制前栈上的__block变量用结构体实例。但是栈上的__block变量用结构体实例在__block变量从栈复制到堆上时,会将成员变量__forwarding的值替换为复制目标堆上的__block变量用结构体实例的地址。如图所示。

复制__block变量.png

通过__forwarding指针,无论是在 Block 语法中、语法外使用__block变量, 无论是__block变量存储在栈上或堆上,都可以顺利地访问同一个__block变量。

截获对象

 int main(int argc, const char * argv[]) {
     void(^blk)(id);
     {
         NSMutableArray *arrM = [NSMutableArray array];
         blk = [^(id obj){
             [arrM addObject:obj];
             NSLog(@"arrM count = %lu", arrM.count);
         } copy];// ARC 时,此 Block 自动 copy
     }
     blk([NSObject new]);
     blk([NSObject new]);
     blk([NSObject new]);
     return 0;
 }

变量作用域结束的同时,变量 arrM 被废弃,其强引用失效,因此赋值给变量 arrM 的 NSMutableArray 类的对象必定被释放并废弃。但是该源代码运行正常,其执行结果如下:

 arrM count = 1
 arrM count = 2
 arrM count = 3

这个结果意味着赋值给变量 arrM 的 NSMutableArray 类的对象在该源代码最后 Block 的执行部分超出其变量作用域而存在。通过编译器转换后的源代码如下(为方便阅读,部分代码改回 OC 代码、并删除强转):

 struct __main_block_impl_0 {
     struct __block_impl impl;
     struct __main_block_desc_0* Desc;
     NSMutableArray *arrM;
 };
 static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj) {
     
     NSMutableArray *arrM = __cself->arrM; 
     [arrM addObject:obj];
     NSLog(@"arrM count = %lu", arrM.count);
 }
 ​
 static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
     _Block_object_assign((void*)&dst->arrM, (void*)src->arrM, 3/*BLOCK_FIELD_IS_OBJECT*/);   
 }
 ​
 static void __main_block_dispose_0(struct __main_block_impl_0*src) {
     _Block_object_dispose((void*)src->arrM, 3/*BLOCK_FIELD_IS_OBJECT*/); 
 }
 ​
 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(int argc, const char * argv[]) {
     void(*blk)(id);
     {
         NSMutableArray *arrM = [NSMutableArray array];
         blk = [&__main_block_impl_0(
             __main_block_func_0, &__main_block_desc_0_DATA, arrM, 570425344
         ) copy];
     }
     *blk->FuncPtr)(blk, [NSObject new]);
     *blk->FuncPtr)(blk, [NSObject new]);
     *blk->FuncPtr)(blk, [NSObject new]);
 ​
     return 0;
 }

请注意被赋值 NSMutableArray 类对象并被截获的自动变量 arrM。我们可以发现它是 Block 结构体中的成员变量。

然而,对象型变量不能作为 C 语言结构体的成员,如果存在 OC 对象型变量,便会引起编译错误。因为编译器不知道何时进行 C 语言结构体的初始化和废弃操作,不能很好地管理内存。除非强制转换为void * (ARC 引起编译错误)或将内存管理修饰符(默认为__strong)改为__ unsafe_unretained该修饰符的变量不属于编译器的内存管理对象。

但是 Block 结构体中可以有__strong修饰的成员变量。因为 OC 运行时库能够准确把握 Block 从栈复制到堆以及堆上的 Block 被废弃的时机,因此 Block 结构体中即使含有附有__strong修饰符或__weak修饰符的变量,也可以恰当地进行初始化和废弃。 为此需要使用在__main_block_desc_0结构体中增加的成员变量 copy 和 dispose ,以及作为指针赋值给该成员变量的__main_block_copy_0函数和__main_block_dispose_0函数。

由于在该源代码的 Block 结构体中,含有附有__strong修饰符的对象类型变量 arrM,所以需要恰当管理赋值给变量 arrM 的对象。因此__main_block_copy_0函数使用__Block_object_assign函数将对象类型对象赋值给Block 结构体的成员变量 arrM 中并持有该对象。

 static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
     _Block_object_assign((void*)&dst->arrM, (void*)src->arrM, 3/*BLOCK_FIELD_IS_OBJECT*/);   
 }

_Block_object_assign函数调用相当于 retain 实例方法的函数,将对象赋值在对象类型的结构体成员变量中。

另外,__main_block_dispose_0函数使用_Block_object_dispose函数相当于release 实例方法的函数,释放赋值在 Block 结构体对象类型成员变量 中的对象。

 static void __main_block_dispose_0(struct __main_block_impl_0*src) {
     _Block_object_dispose((void*)src->arrM, 3/*BLOCK_FIELD_IS_OBJECT*); 
 }

虽然此__main_block_copy_0函数(以下简称 copy 函数)和__main_block_dispose_0函数(以下简称 dispose 函数)指针被赋值在__main_block_desc_0结构体成员变量copy和dispose中,在 Block 从栈复制到堆时以及堆上的 Block 被废弃时会调用这些函数。

函数调用时间
copy 函数栈上的 Block 复制到堆时
dispose 函数堆上的 Block 被废弃时

栈上的Block会复制到堆时机:

●调用 Block 的 copy 实例方法时

●Block 作为函数返回值返回时

●将 Block 赋值给附有__strong修饰符 id 类型的类或 Block 类型成员变量时

●在方法名中含有 usingBlock 的Cocoa框架方法或GCD的 API 中传 Block 时

也就是说,虽然从源代码来看,在上面这些情况下栈上的 Block 被复制到堆上,但其实可归结为_Block_copy函数被调用时 Block 从栈复制到堆。相对的,在释放复制到堆上的 Block 后,谁都不持有 Block 而使其被废弃时调用 dispose 函数。这相当于对象的 dealloc 实例方法。有了这种构造,通过使用附有__strong修饰符的自动变量,Block 中截获的对象就能够超出其变量作用域而存在。虽然这种使用 copy 函数和 dispose 函数的方法在 2.3 节中没做任何说明,但实际上在使用__block变量时已经用到了。

截获对象时和使用_ block 变量时的不同

对象BLOCK_ FIELD_IS_OBJECT(3)
__block 变量BLOCK_ FIELD_IS_ BYREF(8)

通过BLOCK_ FIELD_IS_OBJECTBLOCK_FIELD_IS_BYREF参数,区分 copy 函数和 dispose 函数的对象类型是对象还是__block变量。

但是与 copy 函数持有截获的对象、dispose 函数释放截获的对象相同,copy 函数持有所使用的__block变量,dispose 函数释放所使用的__block变量。

由此可知,Block 中使用的赋值给附有__strong修饰符的自动变量的对象和复制到堆上的__block变量由于被堆上的 Block 所持有,因而可超出其变量作用域而存在。

只有调用_Block_copy函数才能持有截获的附有__strong修饰符的对象类型的自动变量值,不调用_Block_copy函数的情况下,即使截获了对象,它也会随着变量作用域的结束而被废弃。

因此,Block中使用对象类型自动变量时,除以下情形外,推荐调用 Block 的 copy 实例方法。

●Block 作为函数返回值返回时

●将 Block 赋值给类的附有__strong修饰符的 id 类型或 Block 类型成员变量时

●向方法名中含有 usingBlock 的 Cocoa框架方法或 GCD 的API中传递Block时

__block 修饰对象

__block说明符可指定任何类型的自动变量。下面指定用于赋值 OC 对象类型自动变量。

 int main(int argc, const char * argv[]) {
    
     __block NSObject *obj = [NSObject new];
     void(^blk00)(void) = ^{ NSLog(@"%@", obj); };
     blk00();
     
     return 0;
 }

ARC有效时,id 类型以及对象类型变量必定附加所有权修饰符,默认为附有_strong修饰符的变量。该代码换如下:

 static void __Block_byref_id_object_copy_131(void *dst, void *src) {
  _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
 }
 static void __Block_byref_id_object_dispose_131(void *src) {
  _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
 }
 ​
 struct __Block_byref_obj_0 {
     void *__isa;
     __Block_byref_obj_0 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
     NSObject *obj;
 };
 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, id obj) {
   NSLog(@"%@", obj);
 }
 ​
 static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
     _Block_object_assign((void*)&dst->arrM, (void*)src->arrM, 8/*BLOCK_FIELD_IS_BYREF*/);
 }
 ​
 static void __main_block_dispose_0(struct __main_block_impl_0*src) {
     _Block_object_dispose((void*)src->arrM, 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(int argc, const char * argv[]) {
 ​
     __Block_byref_obj_0 obj = {
         (void*)0,
         (__Block_byref_obj_0 *)&obj, 
         33554432, 
         sizeof(__Block_byref_obj_0), 
         __Block_byref_id_object_copy_131, 
         __Block_byref_id_object_dispose_131, 
          [NSObject new]
     };
     void(*blk00)(id) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
     
    blk00->FuncPtr(blk00, (obj.__forwarding->obj));
 ​
     return 0;
 }

在这里出现了,上一节讲到的_Block_object_assign函数和_Block_object_dispose函数。

在 Block中使用附有__strong修饰符的对象类型自动变量的情况下,当 Block 从栈复制到堆时,使用_Block_object_assign 函数,持有 Block 截获的对象。当堆上的 Block 被废弃时,使用_Block_object_dispose函数,释放 Block 截获的对象。

__block变量为附有__strong修饰符的对象类型自动变量的情形下会发生同样的过程。

由此可知,即使对象赋值 copy 到堆上的附有__strong修饰符的对象类型__block变量中,只要__block变量在堆上继续存在,那么该对象就会继续处于被持有的状态。这与 Block 中使用赋值给附有__strong修饰符的对象类型自动变量的对象相同。 另外,我们前面用到的只有附有__strong修饰符的 id 类型或对象类型自动变量。如果使用__weak修饰符会如何呢?首先是在Block中使用附有__weak修饰符的对象类型变量的情况。

 void(^blk)(id);
     {
         NSMutableArray *arrM1 = [NSMutableArray array];
         __weak NSMutableArray *arrM2 = arrM1;
 ​
         blk = [^(id obj){
             [arrM2 addObject:obj];
             NSLog(@"arrM2 count = %lu", arrM2.count);
         } copy];
     }
     blk([NSObject new]);//arrM2 count = 0
     blk([NSObject new]);//arrM2 count = 0
     blk([NSObject new]);//arrM2 count = 0

这是由于附有__strong修饰符的变量 arrM1在该变量作用域结束的同时被释放、废弃,nil 被赋值在附有__weak修饰符的变量 arrM2中。

若同时指定__block说明符和__weak修饰符会怎样呢?

 void(^blk)(id);
     {
         NSMutableArray *arrM1 = [NSMutableArray array];
         __block __weak NSMutableArray *arrM2 = arrM1;
 ​
         blk = [^(id obj){
             [arrM2 addObject:obj];
             NSLog(@"arrM2 count = %lu", arrM2.count);
         } copy];
     }
     blk([NSObject new]);//arrM2 count = 0
     blk([NSObject new]);//arrM2 count = 0
     blk([NSObject new]);//arrM2 count = 0

这是因为即使附加了__block说明符,附有__strong修饰符的变量arrM1也会在该变量作用域结束的同时被释放废弃,nil 被赋值给附有__weak修饰符的变量 arrM2中。

Block 循环引用

如果在 Block 中使用附有__strong修饰符的对象类型自动变量,那么当 Block 从栈复制到堆时,该对象为Block所持有。这样容易引起循环引用。我们来看看下面的源代码:

 //ZYPObject.h
 ypedef void(^blk_t)(void);
 @interface ZYPObject : NSObject {
     blk_t _blk01;
     id _obj02;
 }
 @end
 //ZYPObject.m
 - (instancetype)init {
     if (self = [super init]) {
         _blk01 = ^{NSLog(@"self = %@", self);};
     }
     return self;
 }
 - (void)dealloc {
     NSLog(@"dealloc");
 }
 @end
 //main.m
 int main(int argc, const char * argv[]) {
     ZYPObject *obj = [[ZYPObject alloc] init];
     NSLog(@"%@", obj);
     return 0;
 }

该源代码中 ZYPObject 类的 dealloc 实例方法一定没有被调用。

ZYPObject 类对象的 Block 类型成员变量_blk01持有赋值为 Block 的强引用。即 ZYPObject 类对象持有 Block。 init 实例方法中执行的 Block 语法使用附有__strong修饰符的对象类型变量 self。并且由于 Block 语法赋值在了成员变量_blk01中, 因此通过 Block 语法生成在栈上的 Block 此时由栈复制到堆,并持有所使用的 self。self 持有 Block,Block 持有self。这正是循环引用。

为避免此循环引用,可声明附有__weak修饰符的变量,并将 self 赋值使用。

 - (instancetype)init {
     if (self = [super init]) {
         __weak typeof(self) tmp = self;
         _blk01 = ^{NSLog(@"self = %@", tmp);};
     }
     return self;
 }

在该源代码中,由于Block存在时,持有该 Block 的Z YPObject 类对象即赋值在变量 tmp 中的 self 必定存在,因此不需要判断变量tmp的值是否为 nil.

在面向 iOS4 的应用程序中,必须使用__unsafe_unretained修饰符代替__weak修饰符。在此源代码中也可使用__unsafe_unretained修饰符,且不必担心野指针。

 __unsafe_unretained typeof(self) tmp = self;
 _blk01 = ^{NSLog(@"self = %@",tmp);};

另外,以下源代码中 Bloc k内没有使用 self 也同样截获了 self, 引起了循环引用。

 - (instancetype)init {
     if (self = [super init]) {
         _blk01 = ^{NSLog(@"self = %@", _obj02);};
     }
     return self;
 }

即 Block 语法内使用的_obj02实际上截获了self。对编译器来说,_obj02 只不过是对象用结构体的成员变量。

 _blk01 = ^{NSLog(@"self = %@", self->_obj02);};

该源代码也基本与前面一样,声明附有__weak修饰符的变量并赋值_obj02使用来避免循环引用。在此源代码中也可安全地使用__unsafe_unretained修饰符,原因同上。

 - (instancetype)init {
     if (self = [super init]) {
         __weak typeof(self) tmp = _obj02;
         _blk01 = ^{NSLog(@"self = %@", tmp);};
     }
     return self;
 }

另外,还可以使用__block变量来避免循环引用。

 - (instancetype)init {
     if (self = [super init]) {
         __block typeof(self) block_self = self;
         _blk01 = ^{NSLog(@"self = %@",block_self);};
         block_self = nil;
     }
     return self;
 }

下面我们对使用__block变量避免循环引用的方法和使用__weak修饰符及__unsafe_unretained修饰符避免循环引用的方法做个比较。使用__block变量的优点如下:

●通过__block变量可控制对象的持有期间

●在不能使用__weak修饰符的环境中使用__unsafe_unretained修饰符即可(不必担心野指针)

在执行 Block 时可动态地决定是否将 nil 或其他对象赋值在__block变量中。

使用__block变量的缺点如下: 为避免循环引用必须执行 Block

存在执行了Block语法,却不执行 Block 的路径时,无法避免循环引用。若由于 Block 引发了循环引用时,根据 Block 的用途选择使用__block变量、__weak修饰符或__unsafe_unretained修饰符来避免循环引用。

copy / release

ARC 无效时,一般需要手动将 Block 从栈复制到堆。另外,由于 ARC 无效,所以肯定要释放复制的 Block。这时我们用 copy 实例方法用来复制,用 release 实例方法来释放。

对于存储在栈上的 Block 调用 retain 实例方法则不起任何作用。只有 Block 复制并存储在堆上,才可通过 retain 实例方法持有。因此推荐使用 copy 实例方法用来持有 Block。

由于 Blocks 是 C 语言的扩展,所以在 C 语言中也可以使用 Block 语法。此时使用_Block_copy函数和_Block_release函数代替 copy/release 实例方法。使用方法以及引用计数的思考方式与 OC 中的 copy/release 实例方法相同。

另外,ARC 无效时,__block说明符被用来避免 Block 中的循环引用。这是由于当 Block 从栈复制到堆时,若 Block 使用的变量为附有__block说明符的对象类型的自动变量,不会被 retain ; 若 Block 使用的变量为没有__block说明符的对象类型的自动变量,则被 retain。

截获变量总结

基础变量被截获

将其截获到__main_block_impl_0结构体实例的成员上。

对象变量被截获

将其截获到__main_block_impl_0结构体实例的成员上。

对象型变量不能作为 C 语言结构体的成员,而 Block 结构体中可以有__strong修饰的成员变量。因为 OC 运行时库能够准确把握 Block 从栈复制到堆以及堆上的 Block 被废弃的时机,因此 Block 结构体中即使含有附有__strong修饰符或__weak修饰符的变量,也可以恰当地进行初始化和废弃。

为此需要使用Block的成员__main_block_desc_0结构体中增加的成员变量 copy 和 dispose ,以及作为指针赋值给该成员变量的__main_block_copy_0函数和__main_block_dispose_0函数。

__block基础变量被截获

生成__block变量的结构体(一个新的对象:__Block_byref_val_0),__main_block_impl_0结构体实例(Block)持有这个新的对象,同样,需要使用在__main_block_desc_0结构体中增加的成员变量 copy 和 dispose ,以及作为指针赋值给该成员变量的__main_block_copy_0函数和__main_block_dispose_0函数。

__block对象变量被截获

生成__block变量的结构体(一个新的对象:__Block_byref_obj_0),__main_block_impl_0结构体实例(Block)持有这个新的对象,同样,Block的成员__main_block_desc_0结构体中增加的成员变量 copy 和 dispose ,以及作为指针赋值给该成员变量的__main_block_copy_0函数和__main_block_dispose_0函数。这个新的对象持有被截获的对象,所以__Block_byref_obj_0结构体中增加的成员变量 copy 和 dispose