关于Block循环引用的几种情况和解决方法

循环引用的原因

循环引用问题的根源在于Block和obj可能会互相强引用,互相retain对方,这样就导致了retain cycle,最后这个Block和obj就变成了孤岛,谁也释放不了谁。比如以下几个例子:

几个示例 及 解决方法

@interface Class:()
{
NSString *strVar;//成员变量
}
@end
@implementation Class
@property (nonatomic, copy) NSString *strVVV;//成员属性
- (void)viewDidLoad {
// 强引用示例1
self.myBlock = ^{
[self doSomething];
};
// 强引用示例2
self.myBlock = ^{
strVar = @"haha";
};
// 强引用示例3
self.myBlock = ^{
_strVVV = @"vvvvv";
};
}
@end
+-----------+ +-----------+
| self | | Block |
---> | | --------> | |
| retain 2 | <-------- | retain 1 |
| | | |
+-----------+ +-----------+

说明:

  • 示例1:自己强引用自己
  • 示例2: strVar 是成员变量,属性本身为 strong,同样为强引用。
  • 示例3:这里在 Block 中虽然没直接使用 self,但使用了成员属性。在Block中使用成员属性,retain的不是这个属性,而会retain self,但是这个属性为 strong

解决方法:

__weak typeof (self) weakSelf = self;
// 示例1
self.myBlock = ^{
[weakSelf doSomething];
};
// 示例2
// 成员变量的话,经过测试,不能使用 weakSelf 指向,也不能使用 self-> ,所以,最好还是改为成员属性
// 示例3
self.myBlock = ^{
weakSelf.strVVV = @"vvvvv";
};
+-----------+ +-----------+
| self | | Block |
--X->| | ----X---> | |
| retain 0 | < - - - - | retain 0 |
| | weak | |
+-----------+ +-----------+

另外一个示例:

// 以下代码会存在强引用
BlockTestVC *myController = [[BlockTestVC alloc] init];
myController.testBlock = ^(NSString *str) {
[myController dismissViewControllerAnimated:YES completion:nil];
};
// 解决上述强引用的方法,同样也是添加 __weak
BlockTestVC *myController1 = [[BlockTestVC alloc] init];
BlockTestVC * __weak weakMyViewController1 = myController1;
myController1.testBlock = ^(NSString *str) {
[weakMyViewController1 dismissViewControllerAnimated:YES completion:nil];
};
// 更好的方式是在使用__weak变量前,先用__strong变量把该值锁定
BlockTestVC *myController2 = [[BlockTestVC alloc] init];
BlockTestVC * __weak weakMyController2 = myController2;
myController2.testBlock = ^(NSString *str) {
BlockTestVC *strongMyController = weakMyController2;
if (strongMyController) {
[strongMyController dismissViewControllerAnimated:YES completion:nil];
} else {
}
};

多个对象之间引用问题

retain cycle不只发生在两个对象之间,也可能发生在多个对象之间,这样问题更复杂,更难发现

ClassA* objA = [[ClassA alloc] init];
objA.myBlock = ^{
[self doSomething];
};
self.objA = objA;
+-----------+ +-----------+ +-----------+
| self | | objA | | Block |
| | --------> | | --------> | |
| retain 1 | | retain 1 | | retain 1 |
| | | | | |
+-----------+ +-----------+ +-----------+
^ |
| |
+------------------------------------------------+

解决办法同样是用__weak打破循环引用

ClassA* objA = [[ClassA alloc] init];
__weak typeof(self)weakSelf = self;
objA.myBlock = ^{
[weakSelf doSomething];
};
self.objA = objA;

最后再说一下不会被循环引用的几种情况:

  • UIView 的 animations
  • GCD
  • NSOperation
  • 第三方框架:AFN、Masonry、等。。
  • block不是self的属性或者变量时,在block内使用self也不会循环引用:

    //block不是self的属性时,block内部使用self也不是循环引用
    Animal *animal = [[Animal alloc] init];
    animal.animalBlock = ^(void){
    NSLog(@"animal--> value:%@,address=%p,self=%p",self.person,self.person,self);
    };
  • 把block内部抽出一个作为self的方法,当使用weakSelf调用这个方法,并且这个方法里有self的属性,block不会造成内存泄露

    self.testBlock = ^()
    {
    [weakSelf test];
    };
    -(void)test
    {
    NSLog(@"%@",self.mapView);
    }
  • 当使用类方法有block作为参数使用时,block内部使用self也不会造成内存泄露

    [WDNetwork testBlock:^(id responsObject) {
    NSLog(@"%@",self.mapView);
    }];

关于UIView和AFN不会强引用的原因:

首先block循环引用的条件: block —>强引用(self) self —>强引用(block属性)

UIView的动画block不会造成循环引用的原因就是,这是个类方法,当前控制器不可能强引用一个类,所以循环无法形成。

而AFN无循环是因为绝大部分情况下,你的网络类对象是不会被当前控制器引用的,这时就不会形成引用环。当然我不知道AFN是否做了别的处理,按照这样来说的话,如果你的控制器强引用了这个网络类的对象,而且在block里面引用了当前控制器,也是会发生循环引用的。
其他情况可以自己查询一下

参考内容:http://tanqisen.github.io/blog/2013/04/19/gcd-block-cycle-retain/

推荐阅读:理解 ARC 下的循环引用 http://ios.jobbole.com/82077/