NSTimer 的正确用法你真的知道吗?

33 篇文章 0 订阅
5 篇文章 1 订阅

NSTimer你真的会使用吗?相信每个人都会很自信的说:知道啊!这简单的很,但是你确定你用对了吗?

1. 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方法里面不就可以了吗?

2. NSTimer循环引用

-(void)viewWillDisappear:(BOOL)animated{
	[super viewWillDisappear:animated];

	[self.timer invalidate];
	self.timer = nil;
}

解决循环引用
B: 你看,现在不是循环引用就解决了吗,dealloc也走了,说明对象释放了,没有强引用了。

A: 看似解决了循环引用问题,但是还有其他问题,放在这里viewWillDisappear显然是不合适的,pop 到一半,页面又回弹回去了,这时候定时器失效了,但我页面并没有 pop没有销毁啊!这对用户不友好啊!

B: 那我该怎么办呢?

A: 先来分析一下,一般的话,我们创建一个定时器持有关系如下:
创建定时器的持有关系图
B:那我把target对象对 NSTimer 变为弱引用不就解决了循环持有的问题了吗? 如下:
弱引用关系
A: NO NO NO,不是的。主线程的Runloop在程序运行期间是不会销毁的,它比self的生命周期都长,也就是runloop引用着timer,timer就不会销毁,timer引用着target,target也不会销毁。runloop间距持有了target。如下:
runloop持有关系

那么要打破循环引用,只能从timer引用target这层关系上来打破了。

3. 中间对象解决循环引用

可以使用一个中间对象,对 NSTimertarget进行弱引用,这样就解决了。当 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也走了,说明对象释放了,没有强引用了。

4. 系统api方法解决循环引用

在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. 写在后面

关注我,更多内容持续输出

🌹 喜欢就点个赞吧👍🌹

🌹 觉得有收获的,可以来一波 收藏+关注,以免你下次找不到我😁🌹

🌹欢迎大家留言交流,批评指正,转发请注明出处,谢谢支持!🌹

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卡卡西Sensei

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值