循环引用的原因
循环引用问题的根源在于Block和obj可能会互相强引用,互相retain对方,这样就导致了retain cycle,最后这个Block和obj就变成了孤岛,谁也释放不了谁。比如以下几个例子:
几个示例 及 解决方法
@interface Class:() { NSString *strVar; } @end
@implementation Class @property (nonatomic, copy) NSString *strVVV;
- (void)viewDidLoad { self.myBlock = ^{ [self doSomething]; }; self.myBlock = ^{ strVar = @"haha"; }; 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;
self.myBlock = ^{ [weakSelf doSomething]; };
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]; };
BlockTestVC *myController1 = [[BlockTestVC alloc] init]; BlockTestVC * __weak weakMyViewController1 = myController1; myController1.testBlock = ^(NSString *str) { [weakMyViewController1 dismissViewControllerAnimated:YES completion:nil]; };
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也不会循环引用:
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/