iOS开发 计时器的实现方式

iOS中计时器常用的有两种方式

使用NSTimer类(Swift 中更名为 Timer)

NSTimer 常用的初始化方法区别

方法一

objc
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
swift
class func scheduledTimer(timeInterval ti: TimeInterval, target aTarget: Any, selector aSelector: Selector, userInfo: Any?, repeats yesOrNo: Bool) -> Timer

  • 创建一个创建一个NSTimer对象,并添加到当前的RunLoop中,使用默认模式RunLoop.current.add(timer, forMode: .defaultRunLoopMode),不需要手动fire,系统会自动执行绑定的aSelector
  • 如果设置了repeatstrue,每隔一定的时间会重复执行aSelector,直到手动调用invalidate方法。repeatsfalse,执行一次之后系统会自动调用invalidate
方法二

objc
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
swift
init(timeInterval ti: TimeInterval, target aTarget: Any, selector aSelector: Selector, userInfo: Any?, repeats yesOrNo: Bool)

  • 创建一个创建一个NSTimer对象,需要手动添加到RunLoop中,手动添加到RunLoop中之后开始计时,执行aSelector
  • 如果设置了repeatstrue,每隔一定的时间会重复执行aSelector,直到手动调用invalidate方法。repeatsfalse,执行一次之后系统会自动调用invalidate
方法三 需要iOS 10以上

objc
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
swift
init(timeInterval interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Void)

  • 这个方法和方法二一样,使用block回调。block中自带了NSTimer参数,避免循环引用
方法四 需要iOS 10以上

objc
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
swift
class func scheduledTimer(withTimeInterval interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Void) -> Timer

  • 这个方法和方法一一样,使用block回调。block中自带了NSTimer参数,避免循环引用
注释
  • timer 对象中都是强引用。targetuserInfo,添加到RunLoop中也是强引用timer
  • timer 对象调用invalidate方法后,会从RunLoop中移除引用,timer不会执行对应的方法或者blocktimer会移除RunLoop的引用和userInfotarget的引用,也是为一个方法可以移除timer引用的方法。
  • 官方特别注释
  • You must send this message from the thread on which the timer was installed. If you send this message from another thread, the input source associated with the timer may not be removed from its run loop, which could prevent the thread from exiting properly.大致意思就是计时器在那个线程上创建开始的,就需要在那个线程上调用此方法,不过一般我都是在主线程上创建timer,所以在这也没遇到什么坑
  • fire方法只适合触发重复的计时器,并且不会重置计时器的计时时间。如果不是重复的计时器,触发之后会自动失效,不管计时器时间有没有到
  • 官方注释如下:
  • You can use this method to fire a repeating timer without interrupting its regular firing schedule. If the timer is non-repeating, it is automatically invalidated after firing, even if its scheduled fire date has not arrived.

Timer 内存管理

个人认为内存管理比较坑。Timer对参数的引用都是强引用,而且添加到RunLoop中也是强引用。在ViewController中,controller持有timerRunLoop持有timer,一般在dealloc中调用timer.invalidate,但是此时RunLoop仍然持有timer,并不会走dealloc,导致ViewController无法释放。要求不要的可以在viewWillDisappear:中调用timer.invalidate。这也只能在当前页面使用定时器,离开了这个页面就不行了。网上有折中的方法就是ViewController不持有timer,借助单例类来实现,这只能算折中的方法。因为在项目中一个页面要定时的请求接口刷新数据,只要控制器还在就要请求,上面的方法都不想用,所以使用了GCD的方式实现。

使用GCD

GCD在swift3以后改动很大,所以分来来说

objc

uint64_t interval = 5 * NSEC_PER_SEC;
//创建一个专门执行timer回调的GCD队列
dispatch_queue_t queue = dispatch_queue_create("timerQueue", 0);
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//使用dispatch_source_set_timer函数设置timer参数
dispatch_source_set_timer(_timer, dispatch_time(DISPATCH_TIME_NOW, 0), interval, 0);
//设置回调
__weak __typeof(self) ws = self
dispatch_source_set_event_handler(_timer, ^(){
[ws sendRequest];
});
dispatch_resume(_timer);
dealloc方法
- (void)dealloc
{
if (_timer != NULL) {
dispatch_cancel(_timer);
_timer = NULL;
}
}

  • 使用dispatch_source_create方法生成dispatch_source_t实例,并且设置回调。
  • dispatch_resume(_timer);执行这个方法timer才会开始,默认是suspend暂停、挂起状态
  • dispatch_suspend(_timer);暂停timer,计时器出于暂停状态,重新启用调用dispatch_resume(_timer);

swift

var ti : DispatchSourceTimer!
ti = DispatchSource.makeTimerSource(flags:DispatchSource.TimerFlags.init(rawValue: 0) , queue: nil);
ti.schedule(deadline: DispatchTime.now(), repeating: 2.0)
ti.setEventHandler { [weak self] in self?.timerAction() }
ti.resume()
deinit { if ti != nil { ti.suspend() } }

  • ti.resume()开始计数器,否则不是主动开始
  • ti.suspend()暂停、挂起计时器,重新开始ti.resume()

总结

如果在一个页面里面需要使用定时器,离开页面就停止,个人倾向于使用NSTimer类实现。如果离开页面不能停止还需要继续执行,使用GCD好一点吧,毕竟不会出现内存问题。折中的方法个人是不喜欢的。大家在项目中一定要具体问题具体对待,实现方法有很多,选择自己认为比较好的,没有问题的才是正确的。

转载于:https://juejin.im/post/59e1ab526fb9a0450a666082

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值