通常在做獲取手機驗證碼的功能的時候,點擊獲取驗證碼倒數(shù)60秒才可以重發(fā),這時會用到倒計時,最常用的就是用NSTimer來實現(xiàn)。
但是在客戶端進行倒計時會存在一些問題,比如倒計時正在進行時App被用戶殺掉,再次進入App倒計時就沒有了;而且NSTimer使用不當還會出現(xiàn)內(nèi)存泄露的情況發(fā)生。
為了讓倒計時不受進入后臺,app被終止等操作的影響,制作了一個借助本地化儲存可以持續(xù)運行的倒計時器。
效果如下圖:

124.gif
可以看出來還有點誤差的,對精度要求不太高的倒計時可以接受。
思路如下圖:

本地化倒計時.png
用于儲存倒計時數(shù)據(jù)的結構體:
struct RTCountdown {
var id:String? //計時器id
var fireDate:Date? //倒計時啟動日期
var totalTimeInterval:Double? //總倒計時時長
var currentTimeInterval:Double?//當前已進行時長
}
根據(jù)計時器啟動日期和當前日期的比較獲得已進行時長,由于計算出的數(shù)值有小數(shù),所以導致會出現(xiàn)小于1秒的誤差。
let currentTimeInterval = Date.init().timeIntervalSince(fireDate!)
初始化方法,一個使用代理,一個使用閉包函數(shù),使用閉包函數(shù)時要小心循環(huán)引用;
/// 初始化計時器,采用代理方式回調(diào);使用此方法初始化后,即使設置閉包函數(shù)也不生效
init(identifier:String, owner:NSObject!, delegate:RTCDTimerDelegate!)
/// 初始化計時器,采用閉包函數(shù)方式回調(diào);使用此方法初始化后,即使設置代理也不會調(diào)用代理方法
init(identifier:String, owner:NSObject!, handlerPerSecond:((Int)->Void)?, completion:(()->Void)?)
使用的幾個方法:
/// 開啟新的倒計時,原有的倒計時將被清除
///
/// - Parameters:
/// - seconds: 倒計時長度
/// - owner: 計時器持有者,傳nil則使用初始化時傳入的對象
/// - handlerPerSecond: 每秒回調(diào),傳nil則執(zhí)行初始化時傳入的回調(diào)函數(shù)
/// - completion: 倒計時完畢回調(diào),傳nil則執(zhí)行初始化時傳入的回調(diào)函數(shù)
func startNewCountdown(seconds:Double, owner:NSObject?, handlerPerSecond:((Int)->Void)?, completion:(()->Void)?)
/// 繼續(xù)已有的倒計時,重置回調(diào)函數(shù),若調(diào)用時該id的倒計時已經(jīng)完成或數(shù)據(jù)不存在,則返回false,未完成則返回true,返回false時,handlerPerSecond和completion均不會執(zhí)行
///
/// - Parameters:
/// - owner: 計時器持有者,傳nil則使用初始化時傳入的對象
/// - handlerPerSecond: 每秒回調(diào),傳nil則執(zhí)行初始化時傳入的回調(diào)函數(shù)
/// - completion: 倒計時完畢回調(diào),傳nil則執(zhí)行初始化時傳入的回調(diào)函數(shù)
func fetchCountdown(owner:NSObject?, handlerPerSecond:((Int)->Void)?, completion:(()->Void)?) -> Bool
/// 原有倒計時基礎上延長計時,重置回調(diào)函數(shù),若原有倒計時已完成,則開啟新的倒計時
///
/// - Parameters:
/// - seconds: 延長部分時長
/// - owner: 計時器持有者,傳nil則使用初始化時傳入的對象
/// - handlerPerSecond: 每秒回調(diào),傳nil則執(zhí)行初始化時傳入的回調(diào)函數(shù)
/// - completion: 倒計時完畢回調(diào),傳nil則執(zhí)行初始化時傳入的回調(diào)函數(shù)
func appendCountdown(seconds:Double, owner:NSObject?, handlerPerSecond:((Int)->Void)?, completion:(()->Void)?)
/// 取消倒計時,直接執(zhí)行完成回調(diào),清除本地數(shù)據(jù)
func cancel()
在初始化時傳入了一個owner參數(shù),這個參數(shù)可以為倒計時器的持有者,當持有者被釋放,Timer也將被釋放,防止出現(xiàn)Timer內(nèi)存泄漏的情況。
@objc private func oneCount(){
if owner == nil {
timer?.invalidate()
timer = nil
return
}
還有手動添加Timer至runloop并選擇commonModes可以防止拖動view時Timer卡住的問題。
let t = Timer.init(timeInterval: 1, target: self, selector: #selector(oneCount), userInfo: nil, repeats: true)
RunLoop.current.add(t, forMode: RunLoopMode.commonModes)
Demo地址(這是Swift代碼,后續(xù)會補上OC)