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

NSTimer 的正确用法你真的知道吗?,第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 到一半,页面又回d回去了,这时候定时器失效了,但我页面并没有 pop没有销毁啊!这对用户不友好啊!

B: 那我该怎么办呢?

A: 先来分析一下,一般的话,我们创建一个定时器持有关系如下:

B:那我把target对象对 NSTimer 变为弱引用不就解决了循环持有的问题了吗? 如下:

A: NO NO NO,不是的。主线程的Runloop在程序运行期间是不会销毁的,它比self的生命周期都长,也就是runloop引用着timer,timer就不会销毁,timer引用着target,target也不会销毁。runloop间距持有了target。如下:

那么要打破循环引用,只能从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. 写在后面

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

CSDN掘金简书

🌹 喜欢就点个赞吧👍🌹

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

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

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/997134.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存