本文主要根据《Objective-C高级编程》这本书中的第二章来进行的一个总结,其中包含了查看其它文章后的总结和自己的一些理解,关于 block 的一些定义在这里就不说了,这里主要讲一下 block 中的截获自动变量和 __block
关键字的实现
文章主要内容如下:
- 一个普通的 block
- 截获自动变量值
- __block 修饰符
- 循环引用
一个普通的 block
int main(int argc, char * argv[]) {
void(^blk)() = ^{
printf("Hello World");
};
blk();
return 0;
}
一个最普通的 block,内部没有使用到任何变量,让我们来看一下它的内部结构,通过 clang -rewrite-objc
命令将上面的代码解析为一份 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;
__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("Hello World");
}
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, char * argv[]) {
void(*blk)() = ((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 函数进行分解处理,这样可以更加直观的查看
void(*blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
分解为
struct __main_block_impl_0 tempBlock = __main_block_impl_0(__main_block_func_0, &_main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tempBlock;
可以看出,在编译之后 blk 是一个指向一个 __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 函数可以看到,创建的就是这样一个类型的结构体,我们来看一下这个结构体的内部构造:
- impl:
__block_impl
类型,成员变量之一,内部含有函数指针 - Desc:
__main_block_desc_0
类型指针,成员变量之一,用来描述当前 block 的一些附加信息,例如:结构体的大小 -
__main_block_impl_0
:结构体初始化函数,在结构体中进行成员变量的初始化
接下来我们看一下 impl 所属结构体的主要构造
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
- isa:非常常见的指向类的指针
- FuncPtr:函数指针,在 main 函数的初始化过程中,可以观察到是将一个静态函数
__main_block_func_0
对其赋值
接下来看一下 Desc 指针的结构
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)};
- reserved:今后升级版本所需要区域
- Block_size:block 的大小
调用结构体初始化函数
从源码可以看出,在 main 函数中通过调用结构体的初始化函数来创建一个结构体
__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;
}
我们可以看出,isa 指针保持所属类的结构体的实例的指针,这也说明了 block 其实也就是一个 OC 对象,因为其包含了 isa 指针
截获自动变量值
先来看一段代码:
int a = 10;
void(^blk)() = ^{
printf("%d\n", a);
};
a = 20;
blk();//输出结果:10
我们可以发现,当我们定义一个变量之后,又定义一个 block,并且在 block 内部引用了这个变量,然后在外面修改变量,然后调用 block,得到的变量还是修改前的变量,这是为什么呢?好像是我们定义 block 的时候将使用的变量 copy 了一份然后自己存起来,当调用 block 的时候,使用的变量是我们 copy 的那一份,所以不管外面对变量如何进行修改,都不会影响我们保存的那一份,当然这只是猜想,带着问题走进源码看一看.
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;
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\n", 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, char * argv[]) {
int a = 10;
void(*blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
a = 20;
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
对比前面那个普通的 block,我们发现几个不同点
- 在
__main_block_impl_0
结构体中多了一个成员变量a
, 并且在初始化函数中多了一个参数a
,这个a
就是 block 截获的自动变量值 - 在静态函数
__main_block_func_0
使用截获的自动变量的时候,用到了一个指针_cself
, 这是什么呢?在 main 可以发现这个参数是在进行 block 调用的时候传入的,传入的就是 block 本身,所以说在静态函数中操作的变量也就是在 block 创建的时候的那个成员变量,而不是操作的外部变量,也就是说,block 只是截获的自动变量的值,而并不是截获了其地址,所以外部变量在 block 创建之后进行修改在这里也没什么效果,前面的猜想得证.
这里需要注意,block 只会截获使用到的变量,而对于没有使用到的变量是不会进行截获的.
那是不是所有的变量都是如此呢?我们做测试的是局部变量,但是我们知道,在 C 语言中变量还有全局变量、全局静态变量和局部静态变量,我们使用这些来尝试一下,以下使用局部静态变量做测试,至于全局变量和全局静态变量留给读者自己探索,后面也会进行解释:
static int a = 10;
void(^blk)() = ^{
printf("%d\n", a);
};
a = 20;
blk();//输出20
我们发现输出变了,怎么回事呢?还是看源码
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;
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\n", (*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, char * argv[]) {
static int a = 10;
void(*blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &a));
a = 20;
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
和上面的局部变量作对比,可以发现,在 __main_block_impl_0
结构体中,变成了存储变量 a
的指针,那么我们访问的时候访问的是指针 a
指向的变量,那么就可以解释输出为 20 的原因.
__block 修饰符
当我们修改截获的变量的时候,示例如下:
int a = 10;
void(^blk)() = ^{
a = 20;//报错:Variable is not assignable(missing,__block type specifier)
printf("%d\n", a);
};
blk();
因为在底层实现中,即使在 block 内部对对象进行改变,外部的对象没有任何改变,原因也就是前面的解释的自动变量的截获,所以可能苹果就在编译阶段就告诉开发者这样是无效的,也就是直接编译报错。
那么,现在可以得出一个结论:被截获的自动变量值不能直接进行修改,但是,有两种方法可以解决这个问题:
- 改变存储于特殊存储区域的变量
- 使用
__block
修饰符
我们先来说一下第一种,在 C 语言中变量一般分为五种,分别是:
- 自动变量(局部变量)
- 函数参数
- 静态变量(局部静态变量)
- 静态全局变量
- 全局变量
这里需要去除函数参数这一项,那么写一个测试代码,因为前面已经做过自动变量和静态变量的测试,所以这里只进行全局变量和静态全局变量的测试,如下:
int globalA = 10;
static int intGlobalA = 20;
int main(int argc, char * argv[]) {
void(^blk)() = ^{
globalA += 10;
intGlobalA += 20;
printf("%d %d\n", globalA, intGlobalA);//输出:20 40
};
blk();
return 0;
}
查看输出,发现可以进行改变,我们来看一下源码:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
int globalA = 10;
static int intGlobalA = 20;
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) {
globalA += 10;
intGlobalA += 20;
printf("%d %d\n", globalA, intGlobalA);
}
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, char * argv[]) {
void(*blk)() = ((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;
}
通过源码我们发现,block 并未对两个变量进行截获,而进行访问和修改的时候,并未经过 block,也就是并未使用到 __cself
指针,而是直接进行访问和修改,如下:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
globalA += 10;
intGlobalA += 20;
printf("%d %d\n", globalA, intGlobalA);
}
至此,可以得出一个结论:
- 全局变量:可以直接进行访问,修改
- 静态全局变量:可以直接进行访问,修改
- 静态变量:指针引用,可以进行访问,修改
- 自动变量:值引用,不可进行修改
然后,我们再来看一下第二种,使用 __block
修饰符,使用 __block
修饰符修饰的变量称为 block 变量
测试代码如下:
int main(int argc, char * argv[]) {
__block int a = 10;
void(^blk)() = ^{
a = 20;
printf("%d\n", a);//输出:20
};
blk();
printf("%d\n", a);//输出:20
return 0;
}
可以发现,在使用 __block
修饰符之后 block 内可以修改自动变量了,同样的,我们来查看一下源码的实现:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
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) = 20;
printf("%d\n", (a->__forwarding->a));
}
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, char * argv[]) {
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
void(*blk)() = ((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);
printf("%d\n", (a.__forwarding->a));
return 0;
}
可以发现,在使用 __block
修饰符之后,源码中多了很多东西,我们来一个个进行解析
首先,还是从 __main_block_impl_0
这个结构体开始,可以发现,此时的 block 并不是将截获的 a
直接作为成员变量,而是使用一个结构体 __Block_byref_a_0
类型的同名变量来代替,我们来看一下这个结构体:
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
可以看到,这个结构体中含有一个成员变量 a
,即是截获的自动变量,也就是 __block
修饰的自动变量,这里可以看出,当一个自动变量使用 __block
修饰之后,原来的自动变量被生成一个结构体,而结构体中的一个成员变量持有原来的自动变量
在这个结构体中,可以看到一个 __forwarding
指针,这个指针是指向自己的,它的作用就是无论当前 block 是处于栈中,还是堆中,都能够准确的进行访问,所以后面就是通过 __forwarding
指针来访问成员变量 a
的,这里引用《Objective-C 高级编程》中的一张图片来解释一下:
- 最初 block 在栈上,那么 forwarding 指针指向自身的
__block
变量结构体 - 在 block 被复制到堆上时,会将 forwarding 的值替换为堆上的目标 block 变量用结构体实例的地址,而在堆上的目标 block 变量自己的 forwarding 的值就指向它自己
而在使用变量的时候,如下:
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) = 20;
printf("%d\n", (a->__forwarding->a));
}
可以看出,在调用 block 之后,会生成一个 __Block_byref_a_0
类型的指针指向 block 结 构体中的封装截获变量的结构体成员变量,然后通过 __Block_byref_a_0
结构体中的 __forwarding
指向目标 block 变量进行修改
从源码中我们看到多了两个函数:
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*/);}
在 OC 中,C 语言结构体不能含有附有 __strong
修饰符的变量,因为编译器不知道应该什么时候进行 C 语言结构体的初始化和废弃操作,不能很好的管理内存,但是 OC 中的运行时库能够准确把握 block 从栈复制到堆以及堆上的 block 被废弃的时机,实际上就是使用了 __main_block_copy_0
和 __main_block_dispose_0
这两个函数.
其中,_Block_object_assign
相当于 retain 操作,将对象赋值在对象类型的结构体成员变量中。
_Block_object_dispose
相当于 release 操作。
调用 copy 函数和 dispose 函数的时机:
函数 | 调用时机 |
---|---|
copy | 栈上的 block 复制到堆上时 |
dispose | 堆上的 block 被废弃时 |
栈上的 block 复制到堆上的情况
- 调用 block 的 copy 函数时
在调用 block 的 copy 实例方法时,如果此时 block 在栈上,那么会被复制到堆上 - Block 作为函数返回值返回时
调用_Block_copy
函数 - 将 Block 赋值给附有 __strong 修饰符 id 类型的类或者 Block 类型成员变量时
调用_Block_copy
函数 - 方法中含有 usingBlock 的 Cocoa 框架方法或者 GCD 的 API 中传递 Block 时
调用_Block_copy
函数或者 block 的 copy 实例方法
堆上的 block 被废弃的情况
- 堆上的 Block 被释放后,谁都不再持有Block时调用 dispose 函数
block 的存储域
前面说到,block 可以看做是一个 OC 对象,从源码中,我们可以看到 block 中的 isa 指针指向的是 &_NSConcreteStackBlock
, 那么就可以说,此时 block 的类为 _NSConcreteStackBlock
,和这个类相似的还有:
- _NSConcreteStackBlock(栈区)
- _NSConcreteMallocBlock(堆区)
- _NSConcreteGlobalBlock(数据区)
NSConcreteGlobalBlock
在两种情况下,block 会存在于数据区,即isa 指针指向_NSConcreteGlobalBlock:
- 记述全局变量的地方有 block 语法时
测试代码:
void(^globalBlk)() = ^{};
int main(int argc, char * argv[]) {
return 0;
}
- block 的语法表达式中不使用应该截获的自动变量时
NSConcreteStackBlock
除了以上两种情况外生成的 block 语法生成的 block 都是 NSConcreteStackBlock 类对象
NSConcreteMallocBlock
配置在全局变量上的 block,从变量作用域外也可以通过指针安全的使用,但是设置在栈上的 block,如果其所属的变量作用域结束,该 block 就会被废弃,由于 __block
变量也是在栈上的,同理,如果其所属的变量作用域结束,该变量就会被废弃.
为了解决这个问题,通过将 block 和 __block
变量复制到堆上来解决,这样即使其所属的变量作用域结束,堆上的 block 依然存在,复制到堆上的 block 的 isa 变为 _NSConcreteMallocBlock
三种类型 block 被复制之后:
类型 | 存储位置 | 复制之后 |
---|---|---|
NSConcreteGlobalBlock | 数据区 | 什么也不做 |
NSConcreteStackBlock | 栈上 | 从栈上复制到堆上 |
NSConcreteMallocBlock | 堆上 | 引用计数增加 |
在 block 中使用对象类型自动变量时,除以下几种情况外,推荐手动调用 block 的 copy 方法:
- block 作为函数值返回的时候
- 将 Block 赋值给附有 __strong 修饰符 id 类型的类或者 Block 类型成员变量时
- 向方法中含有 usingBlock 的 Cocoa 框架方法或者 GCD 的 API 中传递 Block 时
当一个 block 被复制到堆上的时候,__block
变量也会被复制到堆上,并且被这个 block 所持有,如果又有 block 被复制到堆上,那么 __block
变量的引用计数会增加,然后如果 block 被废弃,那么就不再持有 __block
变量.
block 循环引用
如果在 block 中使用附有 __strong
修饰符的对象类型自动变量,那么当 block 从栈上复制到堆上的时候,该对象被 block 所持有,这样就容易发生循环引用
例如下面这份代码:
typedef void(^blk)(void);
interface BlockTest : NSObject
{
blk blk_;
}
@implementation BlockTest
- (instancetype)init
{
self = [super init];
blk_ = ^{
NSLog(@"self----------------%@",self);
};
return self;
}
@end
上面这份代码中就出现了循环引用,即BlockTest 类对象持有 block,而在 block 中使用持有 __strong
修饰符的 id 类型变量 self,二者互相持有,导致都无法释放,这就是循环引用
如下图所示:
在编译器进行代码编译的时候,可以检测出这种循环引用,并且发出警告,但是并不是任何情况下都能有警告,这里需要注意
为了避免循环引用,这里有几种方法,我们一个一个看
__weak 修饰符
即如下代码:
- (instancetype)init
{
self = [super init];
id __weak weakSelf = self;
blk_ = ^{
NSLog(@"self---------------%@",weakSelf);
};
return self;
}
加上 __weak
修饰符之后,block 不再是持有 self,而是持有对对象的弱引用,也就是 self 强引用 block,而 block 弱引用 self,这样就不会发生循环引用
__block 修饰符
先看一下如下代码:
- (instancetype)init
{
self = [super init];
__block id tempSelf = self;
blk_ = ^{
NSLog(@"self = %@",tempSelf);
tempSelf = nil;
};
return self;
}
- (void)dellocBlk
{
blk_();
}
在上面的代码中,乍一看二者还是互相引用,但是在 block 内部执行完毕之后有了一个将 tempSelf 置为 nil 的操作,这样就是将 self 对 block 的强引用切断,那么循环引用被破坏,但是这样有一个缺点,那就是如果不执行 blk_ 这个 block,这个循环引用将会永远存在,只有执行了这个 block,tempSelf 才会被置为 nil,循环引用才会被破坏,但是这种方案并非没有优点,很明显的一个优点就是我们可以依靠这个 block 来控制对象的持有时间,这样可以控制对象的释放时间.
Strong-Weak Dance
在解决 block 的循环引用时,除了使用 __weak
修饰符外,还有一个被苹果称为:“Strong-Weak Dance” 的方法,那这个是什么呢?先看一下代码:
- (instancetype)init
{
self = [super init];
id __weak weakSelf = self;
_blk = ^{
id __strong strongSelf = weakSelf;
if (strongSelf) {
NSLog(@"self---------------%@", strongSelf);
}
};
return self;
}
是不是很奇怪,我们好不容易使用 __weak
消除的强引用,为什么在里面又进行了强引用呢?也就是强引用->弱引用->强引用这个过程的作用是什么?我们先来看一下普通的 __weak
有什么问题
__weak BlockTest *weakSelf = self;
self.completionHandler = ^(NSInteger result) {
[weakSelf.property removeObserver: weakSelf forKeyPath:@"pathName"];
};
在上面的代码中,__weak
确实解决了循环引用的问题,但是假设当前 block 在子线程中执行,但是在执行的时候 self 被释放了,这时候 weakSelf 也被置为 nil,但是我们使用 KVO 来进行移除操作,这时候就会发生崩溃
这时候就需要 Strong-Weak Dance 登场了,还是先来看代码:
- (instancetype)init
{
self = [super init];
id __weak weakSelf = self;
_blk = ^{
id __strong strongSelf = weakSelf;
NSLog(@"self---------------%@", strongSelf);
};
return self;
}
在 block 内部对 self 又进行一次强引用之后,self 所指对象的引用计数变为 2 ,即使主线程中的 self 被释放,那么对象的引用计数变为1,此时对象并没有销毁
这时候,可能会有人有问题,在里面对 self 的对象进行了强引用,这样岂不是又变成了互相持有,循环引用?
这个时候需要注意,block 只有截获外部变量时,才会引用它。如果是内部新建一个,则没有任何问题。
但是这样真的能够解决多线程下,weakSelf 指向的对象在 block 执行之前被释放的问题吗?如果在执行 id __strong strongSelf = weakSelf
之前 weakSelf 指向的对象就已经被释放了,那么这个语句就没有任何意义了,在开始的那个 KVO 的例子中,崩溃还是依然会发生的,所以此时的 Strong-Weak Dance 没有任何作用,所以为了保证安全性,我们可以在 block 内部进行 strongSelf 的 nil 检测,即:
- (instancetype)init
{
self = [super init];
id __weak weakSelf = self;
_blk = ^{
id __strong strongSelf = weakSelf;
if (strongSelf) {
NSLog(@"self---------------%@", strongSelf);
}
};
return self;
}
参考资料
- 《Objective-C 高级编程》干货三部曲(二):Blocks篇
- 深入研究Block捕获外部变量和__block实现原理