你的内存泄漏了吗?几个容易被忽视的保留环
保留环高危地带-Block
block隐性强引用self,这个大家都有警惕了,以至于发展出规范的写法:
__weak __typeof(self)weakSelf = self;
__strong __typeof(weakSelf)strongSelf = weakSelf;
(不知道如何使用weakSelf的请后面留言)
所以现在已经较少出错了,本篇列举是一些大家开发中意识不到的地方,各位iOSer可以对照检查自己的项目,看看是否引发了相同的保留环:
NSTimer 强引用
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerEvent:) userInfo:nil repeats:YES];
如上面代码,这个timer并没有被self引用,那么为什么self不会被释放呢?因为timer被加到了runloop中(timer必须加到Runloop的原因),timer又强引用了self,所以timer一直存在的话,self也不会释放:
严格来说,这不是一个保留环,而是一个隐性的强引用链。
解决的方法是找个地方释放timer:
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self.timer invalidate];
self.timer = nil;
}
这里要注意repeat = YES 才会导致强引用
应该是苹果意识到了timer的潜在危险,所以iOS10以后,NSTimer提供了block方法:
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block
这样就可以使用“标准的weakSelf”来防止强引用self了,如果没有低版本兼容要求,强烈推荐使用这种方式启用timer。
CAAnimation 的 delegate 是强引用
以下代码会导致ViewController 不能被释放:
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
animation.fromValue = [NSNumber numberWithFloat:0.2f];
animation.toValue = [NSNumber numberWithFloat:1.0f];
animation.autoreverses = YES;
animation.removedOnCompletion = NO;
animation.duration = 1;
animation.repeatCount = HUGE_VALF;
animation.delegate = self;
[self.view.layer addAnimation:animation forKey:@"animation"];
原因是苹果违反了自己定下的规则:
凡是代理都要设置为weak
苹果给出的理由是 CAAnimation 的事件是异步的,必须强引用才能保证事件发生时执行事件,那么如何解决呢?
这里提供一个简单易用的方法:动画结束时,将 CAAnimation 设为 nil
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
[self.view.layer removeAnimationForKey:@"animation"];
anim = nil;
}
另外可以使用NSProxy等第三方代理来解决,请根据自己的实际需要选择。
NSArray 强引用成员
考虑以下场景,有一组QQ用户和一组QQ群,一个用户可以加到几个群中,群当然也包含很多成员,设计用户和群的类如下:
@class Group;
@interface Person : NSObject
@property(nonatomic, strong) NSMutableArray<Group *> *myGroups;
@end
@class Person;
@interface Group : NSObject
@property(nonatomic, strong) NSMutableArray<Person *> *persons;
@end
加群的过程如下
Group *group = [[Group alloc] init];
Person *person = [[Person alloc] init];
person.myGroups = [NSMutableArray array];
[person.myGroups addObject:group];
group.persons = [NSMutableArray array];
[group.persons addObject:person];
这时候,person 和 group 都不能被释放
,也无法将myGroups 或 persons 声明为weak来打破循环,因为这样数组一旦分配就会被立即释放,也就起不了保存对象的作用了,这下怎么办呢?
我们需要找个中介者,可以使用 NSValue,打破互相引用的状态:
[person.myGroups addObject:[NSValue valueWithNonretainedObject:(id)group]];
[group.persons addObject:[NSValue valueWithNonretainedObject:(id)person]];
Storyboard 上的按钮等控件要声明为 weak
正确的写法如下:
@property(nonatomic, weak) IBOutlet UIButton *btn;
从Storyboard拖出控件声明时,系统会自动声明为weak,但有时自己手动声明时候要注意检查。
小结:
本篇列举了4种容易忽视的强引用导致的内存泄漏:
- NSTimer的target
- CAAnimation的delegate
- NSArray数组成员
- Storyboard的IBOutlet
以上情形都分别给出了简便的解决方案,当然都不是唯一的方法,只要能在适当的时机打破强引用都是可以考虑的,如果你有更“优雅”的解决方案,欢迎在留言写出使用情形和具体解决方案供大家一同学习。
🌹🌹🌹赠人玫瑰,手有余香。🌹🌹🌹