iOS 定时器使用(Timer 和 CGD)

在程序开发中,我们常常借助定时器完成定时任务,比如短信验证码倒计时、运动计时等具有时间序列概念的操作。

最常用的定时方式有Timer 和GCD dispatchTimer

Timer的使用

Timer 官方文档

Timer/NSTimer: 在某个时间间隔之后触发的定时器,向目标对象发送指定的消息。 Timer 的完成往往配合runloop和相应的 mode。

初始化

创建即添加到当前 runloop

通过带 scheduledxxx方法初始化的定时器,创建就会添加到当前 runloop ,且以 default mode的形式。即创建就开启。

比如以下初始化方法

scheduledTimer(timeInterval:invocation:repeats:) 
scheduledTimer(timeInterval:target:selector:userInfo:repeats:)
scheduledTimer(timeInterval:repeats:block:)

创建后需要手动添加到 runloop

通过其他初始化方法创建的定时器,需要手动添加到 runloop(调用add(_:forMode)),通过获取当前或者新建 runloop来添加。如果是新建 runloop 则需要手动开启 runloop 定时器才开启。

init(timeInterval:invocation:repeats:) 
init(timeInterval:target:selector:userInfo:repeats:) 

init(fire:interval:repeats:block)
init(fireAt:interval:target:selector:userInfo:repeats:)

通常我们是建议通过手动添加到 runloop 方式

其他方法和属性

   // 触发定时器,相当于调一次,就会发送一次事件
    open func fire()

    // 启动时间设置,管理启动与停止
    open var fireDate: Date
    // 定时间隔
    open var timeInterval: TimeInterval { get }

    // 用来设置定时器误差的
    @available(iOS 7.0, *)
    open var tolerance: TimeInterval

    // 使定时器失效,(重复性的定时一定记得释放对象之前或者使用完后设置)
    open func invalidate()
    // 检查定时器的有效性
    open var isValid: Bool { get }
    // 携带信息
    open var userInfo: Any? { get }

fire()方法用来马上触发一次定时效果,并不是用于开启定时器,定时器的开启决定于创建和 runloop 的运行。

Timer使用注意点

定时器启动

定时器不是主动启动的,要么创建就启动,要么创建后添加到 runloop 启动(或者通过添加后启动 runloop来启动)。

循环引用

由于 Timer 是一种资源,存在持有关系,一般情况下都会造成页面无法释放问题,关闭页面定时器依旧在运行。

而且这种循环引用使用 weak var timer 依然存在,也无法在 deinit()方法中释放(原因循环引用不走)

解决方法主要有如下:

  1. [推荐] 使用闭包,且在闭包参数使用[weak self], iOS 10+
  2. 通过中间代理[weak self]实现Timer实现弱引用iOS NSTimer 循环引用问题,注意自己通过临时 weak self 变量无法解决循环引用
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { [weak self](_) in  // 使用 weak self
           self?.printLog()
      })

不准确问题

如果将 Timer 添加到默认主线程(即主 runloop)中, 如果主线程执行耗时操作或者切换到其他 mode(比如滑动 scrollView 切换到 TrackMode),此时定时器将不准确。尤其在 ScorllView 中使用 Timer要注意,至少使用 Commonmodes

将 timer 添加到其他手动创建的 runloop 中,且不会在这里进行耗时操作。

Tableview 中计时器的几种实现方式

总结:不建议使用 Timer/NSTimer, 建议使用 GCD 定时器

应用进入后台会停止

一般,点击 Home后,1~3s App就会进入后台,此时就无法执行代码了。

实现短信验证倒计时

示例代码参考

1.创建一个重复定时器

countDownTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timeFire), userInfo: nil, repeats: true)

2.实现定时器的Selector方法

func timeFire(){
    if seconds >= 0 {//second:倒计时全局变量,初始值我设为60s
        getVerCodeTitle = "\(seconds) S"
        getVerCodeTimer.setTitle(getVerCodeTitle, for: .normal) //不能单纯设置titlelabel的text值,会一闪一闪的
        getVerCodeTimer.isUserInteractionEnabled = false
        seconds -= 1
    }else{
        getVerCodeTimer.isUserInteractionEnabled = true
        getVerCodeTitle = "重发验证码"
        getVerCodeTimer.setTitle(getVerCodeTitle, for: .normal)
        countDownTimer.invalidate()//使定时器无效,
        countDownTimer = nil  //必须设置为空,否则计时器还是再走,不懂为什么,而且会导致第二次用的时候加速计时。
        getVerCodeTimer.layer.borderColor = ASSIST_COLOR.cgColor
        getVerCodeTimer.setTitleColor(ASSIST_COLOR, for: .normal)
        // print("定时器状态\(countDownTimer.isValid)")
    }
}

效果图:

点击前
img

点击后(倒计时)

img

使用GCD创建定时器(推荐)

在Swift4中GCD的语法更具有Swift形式,使用起来更方便了。

GCD创建的定时器,使得程序更安全,更准确,减少内存泄漏问题。

示例代码(定时重复性操作):
创建一个定时器

private var seconds = 59 // 倒计时
private var countDownTimer: DispatchSourceTimer =  DispatchSource.makeTimerSource(flags: [], queue: DispatchQueue.main)
private var timerIsSuspend: Bool = false //避免取消暂停的定时器崩溃
let buttton = UIButton()

定时重复操作

func setupCountDown() {
        let interval = 1
        countDownTimer.schedule(deadline: .now(), repeating: .seconds(interval))
        countDownTimer.setEventHandler { [weak self] in
            guard let self = self else { return }
            print("倒计时\(self.seconds)")
            if self.seconds >= 0 {
                let title = "重新发送" + "(\(self.seconds)s)"
                self.buttton.setTitle(title, for: .normal)
                self.buttton.isUserInteractionEnabled = false
                self.seconds -= 1
            } else {
                self.buttton.isUserInteractionEnabled = true
                self.buttton.setTitle(L10n.haAccount38, for: .normal)
                self.countDownTimer.suspend()  //暂停定时器
                self.timerIsSuspend = true     // 标记, 避免失败。
            }
        }  
    }

点击按钮,开启定时器

    @objc
    func tapSendEmailButton() {
        seconds = 59 // 充值倒计时
        countDownTimer.resume()
        timerIsSuspend = false
    }

页面销毁时会自定销毁定时器。

 deinit {
        if timerIsSuspend {
            countDownTimer.resume()
        }
        countDownTimer.cancel()
    }

常用操作

挂起定时器 timer.suspend() 通过resume 重启

唤醒: resume()

取消定时器: timer.cancel() //会销毁,无法重启

最容易崩溃的地方:

// 崩溃一:
gcdTimer.suspend()
gcdTimer = nil

// 崩溃二: 
gcdTimer.suspend()
gcdTimer.cancel()
gcdTimer = nil

解决方案
先resume再cancel

注意: 如果定时器 处于挂起状态,cancel会奔溃; 定时器挂起=唤醒+1【原理参考

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java定时器timer)是一种能够在特定时间间隔或者特定时间点执行某些任务的工具。它可以帮助我们实现很多有用的定时任务,如定时备份数据、定时发送邮件、定时清理缓存等等。 Java中的定时器主要有两种实现方式:一种是使用Timer类,另一种是使用ScheduledExecutorService接口。在本文中,我们主要介绍使用Timer类的实现方式。 使用Timer类创建定时器的步骤如下: 1. 创建一个Timer对象 ```java Timer timer = new Timer(); ``` 2. 创建一个TimerTask对象,用于执行定时任务 ```java TimerTask task = new TimerTask() { @Override public void run() { // 定时任务的具体实现 } }; ``` 3. 调用Timer对象的schedule()方法,设置定时任务的执行时间和执行频率 ```java timer.schedule(task, delay, period); ``` 其中,delay表示任务的延迟时间,单位为毫秒;period表示任务的执行周期,单位也为毫秒。如果period为0,则表示只执行一次任务。 完整的示例代码如下: ```java import java.util.Timer; import java.util.TimerTask; public class MyTimerTask extends TimerTask { @Override public void run() { System.out.println("定时任务执行了"); } public static void main(String[] args) { Timer timer = new Timer(); MyTimerTask task = new MyTimerTask(); timer.schedule(task, 1000, 2000); } } ``` 在上面的示例代码中,我们创建了一个MyTimerTask类,继承了TimerTask类,并实现了run()方法。在main()方法中,我们创建了一个Timer对象和一个MyTimerTask对象,并调用了schedule()方法,设置了任务的延迟时间为1000毫秒,执行周期为2000毫秒。当程序执行到schedule()方法时,定时器会在1000毫秒后开始执行定时任务,并且每隔2000毫秒执行一次。 需要注意的是,当定时任务执行时间超过执行周期时,定时器会等待当前任务执行完毕后立即执行下一次任务,而不是等待执行周期结束再执行。如果我们想要避免这种情况,可以考虑使用ScheduledExecutorService接口来实现定时任务。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值