一、循环引用的产生
1.说明
-- :表示弱引用。
-> :表示强引用。
循环引用可以简单理解为对象A引用了对象B,而对象B又引用了对象A:A -> B -> A,此时双方都同时保持对方的一个引用,导致任何时候双方的引用计数都不为0,双方始终无法释放就造成内存泄漏。
当然不只是两个对象之间相互引用会形成循环引用,多个对象之间相互引用最终形成环同样会形成循环引用。
例如:A->B->C->....->X->B。
2.危害
循环引用对 app 有潜在的危害,会使内存消耗过高,导致内存泄漏,性能变差和 app 闪退等。
二、容易造成循环引用的三个场景
block 、 delegate 、NSTimer
1、delegate
self.tableView.delegate = self;
如果 delegate使用strong修饰就会构成循环引用:self -> tableView -> delegate -> self。
所以在定义delegate属性时使用weak便能解决这一问题:self -> tableView -- delegate -> self。tableView和delegate之间不是强引用,所以构不成循环。
规避delegate循环引用的杀手锏也是简单到哭:定义delegate属性时请用assign(MRC)或者weak(ARC),千万别手贱玩一下retain或者strong。
2.block
(1)并不是所有block都会产生循环引用,block是否产生循环引用是需要我们去判断的,例如
//这样是不会产生循环引用,因为这个block不被self持有,是被UIView的类对象持有,这个block和self没有任何关系,所以可以任意使用self。
[UIView animateWithDuration:0.0 animations:^{
[self viewDidLoad];
}];
(2)self -> reachabilityManager -> block -> self,才会产生循环引用,并且XCode给出了循环引用warning,例如
//self -> reachabilityManager -> block -> self 都是循环引用
self.reachabilityManager.stateBlock = ^(int number){
NSLog(@"%@",self. reachabilityManager);
};
//或者(block内部没有显式地出现"self",只要你在block里用到了self所拥有的东西,一样会出现循环引用!)
self.reachabilityManager.stateBlock = ^(int number){
NSLog(@"%@",_ reachabilityManager);
};
(3)解决block循环引用的方法是使用__weak修饰self,然后在block里使用被修饰后的weakSelf来代替self:
__weak __typeof(self) weakSelf = self;
[self.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
NSLog(@"%@",weakSelf.reachabilityManager);
}];
//但是仅仅使用__weak修饰self存在一个缺陷:__weak可能会导致内存提前回收weakSelf,在未执行NSLog()时,weakSelf就已经被释放了,然后执行NSLog()时就打印(null)。
//所以为了解决这个缺陷,我们需要这样在block内部再用__strong去修饰weakSelf:
__weak __typeof(self) weakSelf = self;
[self.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"%@",strongSelf.reachabilityManager);
}];
我们发现上述这个方法确实解决所有问题,但是可能会有两个不理解的点:
即使用weakSelf又使用strongSelf,这么做和直接用self有什么区别?为什么不会有循环引用?这是因为block外部的weakSelf是为了打破环循环引用,而block内部的strongSelf是为了防止weakSelf被提前释放,strongSelf仅仅是block中的局部变量,在block执行结束后被回收,不会再造成循环引用。
这么做和使用weakSelf有什么区别?唯一的区别就是多了一个strongSelf,而这里的strongSelf会使self的引用计数+1,使得self只有在block执行完,局部的strongSelf被回收后,self才会dealloc。
使用最终方法基本可以解决百分之九十九的block的循环引用问题。
解决 NSTimer 循环引用的方法
3. NSTimer
1、合适的时机启动和销毁 NSTimer
解决 NSTimer 的循环引用,我们首先会想到的方法应该是在 OneViewController dealloc 之前就销毁 NSTimer,这样循环就被打破了。
最简单的方法就是在 viewWillAppear 中启动 NSTimer,然后在 viewWillDisappear 中销毁 NSTimer,成对出现,绝对没有问题。
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.time = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(log) userInfo:nil repeats:YES];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.time invalidate];
self.time = nil;
}
2、Effective Objective-C ”中的52条方法
#import <Foundation/Foundation.h>
@interface NSTimer (YPQBlocksSupport)
+ (NSTimer *)ypq_scheduledTimeWithTimeInterval:(NSTimeInterval)interval
block:(void(^)())block
repeats:(BOOL)repeats;
@end
#import "NSTimer+YPQBlocksSupport.h"
@implementation NSTimer (YPQBlocksSupport)
+ (NSTimer *)ypq_scheduledTimeWithTimeInterval:(NSTimeInterval)interval
block:(void(^)())block
repeats:(BOOL)repeats
{
return [self scheduledTimerWithTimeInterval:interval
target:self
selector:@selector(ypq_blockInvoke:) userInfo:[block copy]
repeats:repeats];
}
- (void)ypq_blockInvoke:(NSTimer *)timer
{
void (^block)() = timer.userInfo;
if(block)
{
block();
}
}
@end
__weak ViewController * weakSelf = self;
[NSTimer ypq_scheduledTimeWithTimeInterval:4.0f
block:^{
ViewController * strongSelf = weakSelf;
[strongSelf afterThreeSecondBeginAction];
}
repeats:YES];
计时器保留其目标对象,反复执行任务导致的循环,确实要注意,另外在dealloc的时候,不要忘了调用计时器中的 invalidate方法。
https://www.jianshu.com/p/41eb11ab4988
https://blog.csdn.net/qq_36557133/article/details/91355258
https://blog.csdn.net/qq_36557133/article/details/87654407
https://www.jianshu.com/p/ddfd1b3c0298
网友评论