1. NSTimer的使用
NSTimer
你真的会使用吗?相信每个人都会很自信的说:知道啊!这简单的很,但是你确定你用对了吗?
A: NSTimer
你真的会使用吗?NSTimer
的循环引用你知道吗?
B: 这还不简单,不就是下面👇这种使用吗,So easy
啊!
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void)timerAction{
NSLog(@"打印了");
}
- (void)dealloc {
[self.timer invalidate];
self.timer = nil;
NSLog(@"** dealloc **");
}
A:你看循环引用了吧!页面 pop了,dealloc
压根就没有走啊!你这设置invalidate
无效啊!
B:我大意了,我没有考虑周全,我再想想啊!嗯。。。。那我在页面消失之前viewWillDisappear
方法里面不就可以了吗?
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[self.timer invalidate];
self.timer = nil;
}
B: 你看,现在不是循环引用就解决了吗,dealloc
也走了,说明对象释放了,没有强引用了。
A: 看似解决了循环引用问题,但是还有其他问题,放在这里viewWillDisappear
显然是不合适的,pop 到一半,页面又回d回去了,这时候定时器失效了,但我页面并没有 pop没有销毁啊!这对用户不友好啊!
B: 那我该怎么办呢?
A: 先来分析一下,一般的话,我们创建一个定时器持有关系如下:
B:那我把target
对象对 NSTimer
变为弱引用不就解决了循环持有的问题了吗? 如下:
A: NO NO NO,不是的。主线程的Runloop
在程序运行期间是不会销毁的,它比self的生命周期都长,也就是runloop引用着timer,timer就不会销毁,timer引用着target,target也不会销毁。runloop
间距持有了target
。如下:
3. 中间对象解决循环引用那么要打破循环引用,只能从timer引用target这层关系上来打破了。
可以使用一个中间对象,对 NSTimer
和 target
进行弱引用
,这样就解决了。当 VC 销毁了,target 也会销毁,可以通过中间对象
来判断 target 是否为 nil。这样的话就可以把 NSTimer 置为无效和 nil,这样也就打破了循环引用的目的了。
话不多多说,看代码。
#import "NSTimer+weakTimer.h"
@interface TimerWeakObject : NSObject
/** target */
@property (nonatomic, weak) id target;
/** timer到期的回调方法selector */
@property (nonatomic, assign) SEL selector;
/** timer */
@property (nonatomic, weak) NSTimer *timer;
- (void)cancel:(NSTimer*)timer;
@end
@implementation TimerWeakObject
- (void)cancel:(NSTimer*)timer {
//判断 target是否存在
if (self.target) {
if ([self.target respondsToSelector:self.selector]) {
[self.target performSelector:self.selector withObject:timer.userInfo];
}
}else{
//不存在就把定时器置为无效,也就是runLoop 对timer强引用的释放
//同时timer也会对TimerWeakObject释放
[self.timer invalidate];
}
}
@end
@implementation NSTimer (weakTimer)
+ (NSTimer*)scheduledWeakTimerWithTimeInterval:(NSTimeInterval)interval target:(id)target selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)repeats {
TimerWeakObject *obj = [[TimerWeakObject alloc]init];
obj.target = target;
obj.selector = selector;
// 创建系统的NSTimer给TimerWeakObject
obj.timer = [NSTimer scheduledTimerWithTimeInterval:interval target:obj selector:@selector(cancel:) userInfo:userInfo repeats:repeats];
return obj.timer;
}
@end
这里是通过创建一个NSTimer
的分类,分类里面创建了一个中间对象,通过中间对象来处理外部的NSTimer
事件。
使用起来也是很简单,一行代码搞定。
self.timer = [NSTimer scheduledWeakTimerWithTimeInterval:1 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
效果非常的明显,dealloc
也走了,说明对象释放了,没有强引用了。
在iOS 10以后系统,苹果针对
NSTimer
进行了优化,使用Block回调方式,解决了循环引用问题。
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"打印了");
}];
- (void)dealloc {
[self.timer invalidate];
self.timer = nil;
NSLog(@"** dealloc **");
}
使用这种系统的 api方法,会执行的dealloc
,只要在dealloc
里面进行相关取消定时器的 *** 作就就可以了。
还有下面👇一种方式,这种适合 push的页面,不适应present的。
//生命周期 移除VC的时候,这种适合 push的页面,不适应Present
- (void)didMoveToParentViewController:(UIViewController *)parent {
if (parent == nil) {
[self.timer invalidate];
self.timer = nil;
}
}
如果出去面试,面试官问你你
NSTimer
如何正确使用,你应该知道怎么回答了吧!(推荐中间对象这种回答,Ps:为什么呢?你懂的,装逼啊!😁)
其实方法有很多种,这里就不一一介绍了,老铁们还知道哪些方法呢?欢迎大家在评论区留言讨论。
5. 写在后面CSDN掘金简书关注我,更多内容持续输出
🌹 喜欢就点个赞吧👍🌹
🌹 觉得有收获的,可以来一波 收藏+关注,以免你下次找不到我😁🌹
🌹欢迎大家留言交流,批评指正,
转发
请注明出处,谢谢支持!🌹
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)