请耐心把这篇文章看完,你对 Block 会有更深刻的了解。
这里直接用一个需求来探究循环引用的问题:如果我想在Block中延时来运行某段代码,这里就会出现一个问题,看这段代码:
- (void)viewDidLoad {
[super viewDidLoad];
MitPerson*person = [[MitPerson alloc]init];
__weak MitPerson * weakPerson = person;
person.mitBlock = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakPerson test];
});
};
person.mitBlock();
}
直接运行这段代码会发现[weakPerson test];并没有执行,打印一下会发现,weakPerson 已经是 Nil 了,这是由于当我们的 viewDidLoad 方法运行结束,由于是局部变量,无论是 MitPerson 和 weakPerson 都会被释放掉,那么这个时候在 Block 中就无法拿到正真的 person 内容了。
按如下方法修改代码:
- (void)viewDidLoad {
[super viewDidLoad];
MitPerson*person = [[MitPerson alloc]init];
__weak MitPerson * weakPerson = person;
person.mitBlock = ^{
__strong MitPerson * strongPerson = weakPerson;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[strongPerson test];
});
};
person.mitBlock();
}
这样当2秒过后,计时器依然能够拿到想要的 person 对象。
二、深入探究原理
这里将会对每行代码逐步进行说明
1、开辟一段控件存储 person 类对象内容,创建 person 强指针。
MitPerson*person = [[MitPerson alloc]init];
2、创建一个弱指针 weakPerson 指向person对象内容
__weak MitPerson * weakPerson = person;
person.mitBlock = ^{
3、在 person 对象的 Block 内部创建一个强指针来指向 person 对象,为了保证当计时器执行代码的时候,person 对象没有被系统销毁所以我们必须在系统内部进行一次强引用,并用 GCD 计时器引用 strongPerson,为了保留 person 对象,在下面会对这里更加详细的说明。
__strong MitPerson * strongPerson = weakPerson;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[strongPerson test];
});
};
4、执行 Block 代码
person.mitBlock();
下面将详细分析一下下面这段代码:
person.mitBlock = ^{
__strong MitPerson * strongPerson = weakPerson;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[strongPerson test];
});
};
首先需要明白一些关于 Block 的概念:
1、默认情况下,block 是放在栈里面的
2、一旦blcok进行了copy操作,block的内存就会被放在堆里面
3、堆立面的block(被copy过的block)有以下现象
1> block内部如果通过外面声明的强引用来使用,那么block内部会自动产生一个强引用指向所使用的对象。
2> block内部如果通过外面声明的弱引用来使用,那么block内部会自动产生一个弱引用指向所使用的对象。
我们进行这段代码的目的:
首先,我们需要在 Block 块中调用,person 对象的方法,既然是在 Block 块中我们就应该使用弱指针来引用外部变量,以此来避免循环引用。但是又会出现问题,什么问题呢?就是当我计时器要执行方法的时候,发现对象已经被释放了。
接下来就是为了避免 person 对象在计时器执行的时候被释放掉:那么为什么 person 对象会被释放掉呢?因为无论我们的person强指针还是 weakPerson 弱指针都是局部变量,当执行完ViewDidLoad 的时候,指针会被销毁。对象只有被强指针引用的时候才不会被销毁,而我们如果直接引用外部的强指针对象又会产生循环引用,这个时候我们就用了一个巧妙的代码来完成这个需求。
首先在 person.mitBlock 引用外部 weakPerson,并在内部创建一个强指针去指向 person 对象,因为在内部声明变量,Block 是不会强引用这个对象的,这也就在避免的 person.mitBlock 循环引用风险的同时,又创建出了一个强指针指向对象。
之后再用 GCD 延时器 Block 来引用相对于它来说是外部的变量 strongPerson ,这时延时器 Block 会默认创建出来一个强引用来引用 person 对象,当 person.mitBlock 作用域结束之后 strongPerson 会跟着被销毁,内存中就仅剩下了 延时器 Block 强引用着 person 对象,2秒之后触发 test 方法,GCD Block 内部方法执行完毕之后,延时器和对象都被销毁,这样就完美实现了我们的需求。
最后再用一张图来阐述各个指针、Block 与对象之间的关系
黑色代表强引用,绿色代表弱引用
总结:person.mitBlock 中创建 strongPerson 是为了能够使 GCD Block 保存 person 对象,创建 strongPerson 时候使用 weakPerson 是为了避免 mitBlock 直接引用外部强指针变量所造成的循环引用。
Block循环引用.png