前语

这篇文章会有一点长,会从以下几个点进行对 Block 的分析(当然也是基于源码):

  • Block 的界说
  • Block 底层完成
  • Block 的本质
  • Block 怎么截获主动变量
  • __block 的原理
  • Block 形成的循环引证问题及处理方案

Blocks 的界说

Block 的界说是:带有主动变量(局部变量)的匿名函数。

所以匿名函数便是不带姓名的函数,带有主动变量值表现为 “截获主动变量值”。

比方:

int a = 0;
void (^blk)(void) = ^{
  printf("%d", a);
};
a = 1;
blk();

^{ ... } 其实便是一个匿名函数,也便是 Block。

在将 a 赋值1 之后再履行 blk,发现输出是:

0

也便是在界说 blk 时,就现已捕获 a 其时的值,也便是 0,这便是截获主动变量值。

Blocks 的完成

Block 其实编译之后,便是一段一般的 C 言语代码。而 Block 的本质,便是一个 Objective-C 的目标。

比方下面一段代码:

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

编译之后的代码为:

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("Block\n");
}
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[]) {
  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;
}

大致的结构如下图:

Tip6 - Block

其实也不杂乱,咱们逐一击破。

首先 Blocks 中的函数,在 C 言语中被转化成 __main_block_func_0,能够看到里面履行的办法便是 printf("Block\n"),所以其实 Blocks 运用的匿名函数实际上是被转化成了一个简略的 C 函数,它的命名规则是根据 Blocks 所属的函数名(这儿是 main) 和该 Blocks 语法在该函数中出现的顺序值(这儿是 0)来命名的。

这个函数还有一个参数:struct __main_block_impl_0 *__cself__cself 是结构体 __main_block_impl_0 的指针。

去掉结构函数,__main_block_impl_0 的界说如下:

struct __main_block_impl_0 {
 struct __block_impl impl;
 struct __main_block_desc_0* Desc;
}
struct __block_impl {
 void *isa;
 int Flags;
 int Reserved;
 void *FuncPtr;
};
static struct __main_block_desc_0 {
 size_t reserved;
 size_t Block_size;
}

两个成员分别对应两个结构体:__block_impl__main_block_desc_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;
 }

_NSConcreteStackBlock 是栈 Block,这个也放在后边聊,看一下结构函数是怎么调用的,在 main 办法中:

// 去掉转化的部分
struct __main_block_imp_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 结构体实例的指针,赋值给 __main_block_impl_0 结构体指针类型的变量 blk,以下为这部分代码对应的最初源代码:

void (^blk)(void) = ^{ printf("Block\n"); };

将 Block 语法生成的 Block 赋给 Block 类型变量 blk,它等同于将 __main_block_impl_0 结构体实例的指针赋给变量 blk。该代码中的 Block 便是 __main_block_impl_0 结构体类型的主动变量,即栈上生成的 __main_block_impl_0 结构体实例。

下面就来看看 __main_block_impl_0 结构体实例结构参数:

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

运用 __main_block_impl_0 结构体实例的大小,进行初始化。

假如打开 __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;

这儿是将 __main_block_func_0 的函数指针赋值给了成员变量 FuncPtr

在最初的代码中,调用 Block 的函数是:

blk();

它被转化成了:

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

去掉转化的部分为:

(*blk->imp1.FuncPtr)(blk);

这便是简略的运用函数指针调用函数。正如咱们前面说到的,由 Block 语法转化的 __main_block_func_0 函数的指针被赋值到了 FuncPtr 中。别的,__main_block_func_0 函数还有一个参数 _cself,这儿传递的是 blk,也便是 _cself 指向了 Block 值。

到这儿现已解说清楚了 Block 在底层是怎么完成的。下面看一下咱们前面留下的问题,_NSConcreteStackBlock 是什么。

关于 _NSConcreteStackBlock

isa = &_NSConcreteStackBlock;

将 Block 指针赋给 Block 的结构体成员变量 isa

为了了解它,咱们先来阐明一下 Objective-C 中类和目标的本质。

在 Objective-C 用于存储目标的类型是 id,在 runtime 源代码中的声明如下:

typedef struct objc_object *id;
struct objc_object {
    Class isa;
}

Class 也是一个结构体,继承自 objc_object

typedef struct objc_class *Class;
struct objc_class : objc_object {
}

对于一个 Objective-C 的类:

@interface MyObject : NSObject 
{
    int val0;
    int val1;
}
@end

该类的目标编译后如下:

typedef struct objc_object MyObject;
struct MyObject_IMPL {
    Class isa;
    int val0;
    int val1;
};

MyObject 类的实例变量 val0val1 被直接声明为目标的结构体成员。Objective-C 中由类生成目标,意味着由类生成该目标的结构体实例。生成的各个目标,其实便是由该类生成的目标的各个结构体实例,然后这些结构体实例经过成员变量 isa 坚持了该类的结构体实例指针。

MyObject_IMPL 能够了解为类的元数据,它会经过类型的办法与类的 objc_class 结构体连接起来,MyObject_IMPL 的内存大小在编译时就确认了,能够看到它包含了成员变量,这也能够解说为什么不能在分类中添加成员变量,因为类生成的结构体在编译时就现已确认了内存,不允许再修正,而分类是在运行时才会动态加载,此刻是不能改动到编译时就现已确认的类的内存的。

这儿所生成的 MyObject_IMPL 持有的 isa,指向的便是 MyObject 这个类的 object_class 结构体实例,在这个结构体实例中持有了声明的成员变量、办法的称号、办法的完成(函数指针),属性以及父类的指针:

struct objc_class: objc_object {
    Class isa; // 继承自 objc_object
    Class superclass; // 父类
    cache_t cache; // 办法缓存
    class_data_bits_t *bits; // 办法完成等
}

回到刚刚的 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 结构体的 Objective-C 类目标的结构体。别的,对其间的成员变量 isa 进行初始化,详细如下:

isa = &_NSConcreteStackBlock;

_NSConcreteStackBlock 相当于 objc_class 结构体实例。在将 Block 作为 Objective-C 的目标处理时,关于该类的信息放在 _NSConcreteStackBlock 中。

到这儿,应该就能阐明为什么 Block 便是 Objective-C 的目标了。

截获主动变量值的底层完成

来看下面一段代码:

int main()
{
    int a = 0;
    int b = 99;
    void (^blk)(void) = ^{
        printf(@"%d", a);
    };
    a = 1;
    blk();
}

转化后:

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;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy
  printf("%d", a);
}
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 a = 0;
    int b = 99;
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
    a = 1;
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

Block 语法表达式中运用的主动变量被作为成员变量追加到了 __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 结构体内声明的成员变量类型与主动变量类型完全相同。请注意,Block 语法表达式中没有运用的主动变量不会被追加,变量 b 就没有追加。主动变量捕获只针对 Block 中运用的主动变量。

结合 main 中的调用和 __main_block_impl_0 的界说,能够得到 __main_block_impl_0 结构体实例的初始化如下:

impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_func_0;
Desc = &__main_block_0_DATA;
a = 0;

由此可见,在 __main_block_impl_0 结构体实例(即 Block)中,主动变量值被捕获。

再看 ^{ printf("%d", a); } 办法,该源代码转化为以下函数:

static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
    int a = __cself->a;
    printf("%d", a);
}

这儿运用的,是被捕获到 __main_block_impl_0 结构体中的成员变量上的主动变量 a,也便是 0。

所以,所谓 “截获主动变量值”,意味着在履行 Block 语法时,Block 语法表达式所运用的主动变量值被保存到 Block 的结构体实例(即 Block 本身)中。

__block

主动变量值捕获只能保存履行 Block 语法其时的值,保存后就不能改写该值:

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

这样会报错:

Tip6 - Block

Variable is not assignable (missing __block type specifier)

假如想在 Block 中修正 a,就需求在 a 被界说的时分加上 __block 阐明符:

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

这样就没有问题了。

咱们需求处理两个问题:

  1. 为什么会报错,
  2. 为什么运用 __block 就能够了

你能够想象之前的变量 int a 是 A,被 Block 截获后的变量 block a 是 B,两个是不同的东西,在 Block 内操作的都是 B,就算你真的能够在 Block 内修正,改的也是 B 而不是 A,可是在运用者看来,它们都是 A,所以编译器在遇见这种行为时,直接报错,不允许修正。

Tip6 - Block

__block 阐明符更准确的表述是 “__block 存储域类阐明符”,C 言语中有以下存储域类阐明符:

  • typedef
  • extern
  • static
  • auto
  • register

它们用于指定将变量值设置到哪个存储域中。例如,auto 表明作为主动变量存储在栈中,static 表明作为静态变量存储在数据区中。

咱们编译一下运用 __block 后的代码:

struct __Block_byref_a_0 {
  void *__isa;
  __Block_byref_a_0 *__forwarding;
  int __flags;
  int __size;
  int a;
};
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;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref
  (a->__forwarding->a) = 1;
}
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*/);}
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[]) {
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 0};
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

能够看到 a 变成了一个结构体:

struct __Block_byref_a_0 {
  void *__isa;
  __Block_byref_a_0 *__forwarding;
  int __flags;
  int __size;
  int a;
};

最后的成员变量 a 相当于原主动变量的成员变量。

而赋值的代码:^{ a = 1; } 转化成了:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref
  (a->__forwarding->a) = 1;
}

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

__Block_byref_a_0 结构体实例的成员变量 __forwarding 持有 “指向该实例本身” 的指针。经过成员变量 __forwarding 拜访成员变量 a。(成员变量 a 是该实例本身持有的变量,它相当于原主动变量。)

别的,__block 变量生成的 __Block_byref_a_0 结构体并不在 __main_block_impl_0 结构体中,这样做是为了在多个 Block 中运用 __block 变量。

也便是 int a,咱们先称它为 A,被 __block 阐明符润饰之后,直接变成了别的一个变量 C,之后不管在 Block 内运用仍是 Block 外运用的,都是变换后的 C,所以在 Block 内能够修正,因为改的都是同一个东西。

Tip6 - Block

比方:

int main()
{ 
    __block int a = 0;
    a = 1;
}

转化后的代码为:

struct __Block_byref_a_0 {
  void *__isa;
  __Block_byref_a_0 *__forwarding;
  int __flags;
  int __size;
  int a;
};
int main() {
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 0};
    (a.__forwarding->a) = 1;
    return 0;
}

Block 存储域

经过前面的阐明,咱们能够知道Block 会转化为 Block 的结构体类型的主动变量,__block 变量会转化为 __block 变量的结构体类型的主动变量。所谓结构体类型的主动变量,即栈上生成的结构体的实例。

而且也知道 Block 其实便是 Objective-C 中的目标,之前的例子中,咱们生成的 Block 对应的类为 _NSConcreteStackBlock,在变换后的源码中没有关于它的信息,但有很多与之相似的类:

  • _NSConcreteStackBlock(目标存放在栈上)
  • _NSConcreteGlobalBlock(与全局变量相同,存放在数据区域(.data 区))
  • _NSConcreteMallocBlock(存放在由 malloc 函数分配的内存块(堆)中)

Tip6 - Block

到目前为止咱们 Block 例子中运用的都是 _NSConcreteStackBlock 类,且都设置在栈上。事实上当然并非都是这样的,比方在记述全局变量的当地运用 Block 语法时,生成的 Block 为 _NSConcreteGlobalBlock 类目标,例如:

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

这段代码也会像之前相同,转化为一个结构体,结构体中的 isa 初始化如下:

impl.isa = &_NSConcreteGlobalBlock;

此 Block 的结构体实例放置在程序的数据区域中。

当:

  • 记述全局变量的当地有 Block 语法时
  • Block 语法的表达式中不运用应截获的主动变量时

Block 为 _NSConcreteGlobalBlock 目标,除此之外的 Block 语法生成的 Block 为 _NSConcreteStackBlock 类目标,且设置在栈上。

那么堆上的 _NSConcreteMallocStack 在啥时分用呢?

设置在栈上的 Block,假如其所属的变量效果域完毕,则该 __block 变量也会被抛弃:

Tip6 - Block

但 Block 有一个特性,便是它在超出变量效果域的时分仍可运用。

这是经过将 Block 和 __block 变量从栈上复制到堆上来完成的,将在栈上的 Block 复制到堆上,这样即使 Block 语法地点的变量效果域完毕,堆上的 Block 还能够继续存在:

Tip6 - Block

咱们来看一下复制的进程,下面有一段回来 Block 的函数:

typedef int (^blk)(int);
blk_t func(int rate) {
    return ^(int count) {
        return rate * count;
    };
}

该代码为回来装备在栈上的 Block 的函数,程序履行中从该函数回来函数调用方时变量效果域完毕,因而栈上的 Block 也被抛弃,虽然有这样的问题,可是该代码经过编译器转化如下:

blk_t func(int rate)
{
    blk_t tmp = &__func_block_impl_0(
        __func_block_func_0, &__func_block_desc_0_DATA, rate);
    tmp = objc_retainBlock(tmp);
    return objc_autoreleaseReturnValue(tmp);
}

查看 objc4 的源码,发现 objc_retainBlock 函数实际上便是 _Block_copy 函数:

/**
* 将经过 Block 语法生成的 Block,
* 即装备在栈上的 Block 结构体实例
* 赋值给相当于 Block 类型的变量 tmp 中
*/
tmp = _Block_copy(tmp);
/**
* _Block_copy 函数
* 将栈上的 Block 复制到堆上。
* 复制后,将堆上的地址作为指针赋值给变量 tmp
*/
return objc_autoreleaseReturnValue(tmp);
/**
* 将堆上的 Block 作为 Objective-C 目标
* 注册到 autoreleasepool 中,然后回来该目标
*/

将 Block 作为函数回来值回来时,编译器会主动生成复制到堆上的代码。

大多数状况下编译器会进行恰当的判别,除此之外的状况下需求手动生成,将 Block 从栈上复制到堆上,此刻需求运用 copy 办法。

编译器不能判别的状况:

  • 向办法或函数的参数中传递 Block 时

不过这种状况下还有一些是编译器能够判别的:

  • Cocoa 结构的办法且办法名中含有 usingBlock
  • GCD 的 API

按装备 Block 的存储域,调用 copy 办法:

Tip6 - Block

__block 变量存储域

当 Block 从栈上被复制到堆上时,其间的 __block 变量也会一并复制到堆上。

当 Block 还在栈上时,__block 是作为栈中的一个独自的结构体存在,但 Block 被赋值到堆上时,Block 会去持有 __block 变量。

Tip6 - Block

当多个 Block 运用 __block 时也是相同,在栈上时,__block 变量依然是作为一个独自的结构体存在,当被复制到堆上后,每多一个引证都会添加 __block 的引证计数。

Tip6 - Block

假如装备在堆上的 Block 被抛弃,那么它所运用的 __block 变量也就被开释。

Tip6 - Block

当栈上的 __block 被赋值到堆上时,会将它结构体中的成员变量 __forwarding 的值替换成堆上的 __block 的结构体实例地址,所以无论是在 Block 内仍是 Block 外运用 __block,也不管是在栈上仍是堆上,都能够正确的拜访同一个 __block 变量。

Tip6 - Block

以下状况,栈上的 Block 会复制到堆上:

  • 调用 Block 的 copy 实例办法时
  • Block 作为函数回来值回来时
  • 将 Block 赋值给附有 __strong 润饰符 id 类型的类或 Block 类型成员变量时
  • 在办法名中含有 usingBlock 的 Cocoa 结构办法或 GCD 的 API 中传递 Block 时

Block 的循环引证

假如在 Block 中运用附有 __strong 润饰符的目标类型主动变量,那么当 Block 从栈赋值到堆时,该目标为 Block 所持有,这样容易引起循环引证。

typedef void (^blk_t)(void);
@interface MyObject : NSObject
{
  blk_t _blk;
}
@end
@implementation MyObject
- (instancetype)init {
  if (self = [super init]) {
    _blk = ^{ NSLog(@"self = %@", self); };
  }
  return self;
}
@end
int main(int argc, const char * argv[]) {
  MyObject *o = [[MyObject alloc] init];
  NSLog(@"%@", o);
  return 0;
}

这段代码履行后,MyObjectdealloc 办法不会被调用,也便是 MyObject 不会被开释。

因为 MyObject 持有 Block,而在 init 办法中履行的 Block 语法里,运用了附有 __strong 润饰符的 id 类型变量 self,而且因为 Block 语法赋值给了成员变量 _blk,会导致 Block 从栈上被赋值到堆,并持有所运用的 selfself 持有 Block,Block 持有 self,这便是循环引证。

Tip6 - Block

能够运用 weak 来避免循环引证:

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

Tip6 - Block

一些面试题

体系的 block 中运用 self 为什么能够不必弱引证?

[UIView animateWithDuration:duration animations:^{
    [self.superView layoutIfNeed];
}];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
    self.a = 10;
}];
...

这些体系的 Block 不会形成循环引证的原因是,这些是类办法,Block 不被 self 所持有,而是被其他目标持有,所以不会形成循环引证。

Tip6 - Block

GCD 中的 api 中运用 self 为什么能够不必弱引证?

很简略,因为 self 并不持有 GCD 的东西,只是 GCD 的 Block 持有了 self 罢了,所以不会有循环引证的问题。

Tip6 - Block

为什么 Block 中还需求写一个 strong self?

在避免循环引证的时分运用了 __weak 润饰符,然后再在 Block 内部运用 __strong 去润饰:

- (instancetype)init {
  if (self = [super init]) {
    id __weak tmp = self;
    _blk = ^{
      id __strong strongTmp = tmp;
      NSLog(@"self = %@", strongTmp);
    };
  }
  return self;
}

这是为了避免在 Block 履行的进程中,self 现已被开释了,这儿对应的是 tmp,咱们选用 __weak 润饰的 tmp 在开释后会被设置为 nil,Objective-C 不会履行 nil 所调用的那些办法,程序倒不会崩溃。可是履行的成果可能会和咱们预想的不一致,因为咱们是想在 Block 中去输出关于 self 的一些内容,但假如 self 被开释了,咱们的输出就都是 nil,相似这种需求 Block 在履行时,所引证的目标不会被开释的状况,就能够运用 __strong 再润饰一次,这儿被 __strong 所润饰后的 strongTmp,就像一个在 Block 内部界说的主动变量,跟随 Block 的生命周期,在 Block 履行完毕后,strongTmp 也会被开释。