公司需要做限時搶購的業(yè)務(wù)汇陆,這里面有兩個需求點:
1.在多個cell中顯示倒計時
在每個cell中添加定時器是不現(xiàn)實的怒炸,必定會增加許多性能開銷,所以肯定是使用一個定時器毡代,關(guān)鍵在于如何通知到cell刷新UI
2.本地時間可能和服務(wù)器時間存在誤差
有的手機可能時間沒有和網(wǎng)絡(luò)同步阅羹,或者用戶故意調(diào)整了時間,所以本地時間存在錯誤的可能教寂,所以就定下使用服務(wù)器時間
1.在多個cell中顯示倒計時
思路是這樣的:將需要接收定時器通知的對象注冊到定時器單例中捏鱼,存放在數(shù)組里面,當(dāng)定時器更新的時候遍歷數(shù)組回調(diào)通知
定時器的創(chuàng)建
注意:默認暫停定時器,定時器默認是加載到當(dāng)前runloop中的孝宗,在進行UI界面操作比如滑動列表時穷躁,由于在main runloop中NSTimer是同步交付的被“阻塞”耕肩,就會導(dǎo)致NSTimer計時出現(xiàn)延誤因妇。
解決這種延誤的方法,一種是在子線程中進行NSTimer的操作猿诸,在主線程中修改UI界面顯示操作結(jié)果婚被;另一種是仍然在主線程中進行NSTimer操作,但是將NSTimer實例加到main runloop的特定mode(模式)中梳虽。避免被復(fù)雜運算操作或者UI界面刷新所干擾址芯。
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: {[unowned self] (_) in
self.onTimer()
})
//下面這種方法要求onTimer是@objc
//Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(onTimer), userInfo: nil, repeats: true)
注冊通知
這里使用NSHashTable存放注冊對象的數(shù)組,可以防止循環(huán)引用注冊對象釋放不掉
swift的protocol是一個很好的東西窜觉,這樣可以更好的規(guī)范誰可以注冊通知
@objc
protocol TimerListener: class {
func didOnTimer(announcer: YZTimerUtil, timeInterval: TimeInterval)
}
private let map: NSHashTable<TimerListener> = NSHashTable<TimerListener>.weakObjects()
何時注冊刪除通知
一開始是在willDisplay谷炸、didEndDisplaying方法中進行通知注冊的,后來發(fā)現(xiàn)沒有必要禀挫,因為cell創(chuàng)建后就是要接收通知的willDisplay旬陡、didEndDisplaying中還要進行cell類型的判斷,所以就改為cell- init/deinit中
deinit {
YZTimerUtil.sharedInstance.removeListener(listener: self)
}
//這里cell使用的是xib
override func awakeFromNib() {
super.awakeFromNib()
YZTimerUtil.sharedInstance.addListener(listener: self)
}
2.本地時間可能和服務(wù)器時間存在誤差
這里看項目需求吧语婴,如果項目對時間要求沒有那么嚴格描孟,不做服務(wù)器時間對比也行驶睦,反正服務(wù)器那邊會進行判斷的,有些對時間要求嚴格的肯定是要做對比的匿醒,比如手機手令的動態(tài)碼
我的思路是在定時器初始化的時候進行網(wǎng)絡(luò)請求场航,拿到服務(wù)器的當(dāng)前時間,然后計算本地和服務(wù)器時間的差值廉羔,后面就用這個差值進行計算溉痢。當(dāng)然,受網(wǎng)絡(luò)狀態(tài)的影響憋他,這個時間可能也不是準確的時間适室,但是這個時間誤差會在一個可控范圍內(nèi),為了精確時間差举瑰,可以每隔一段時間就校準一次捣辆,如果要更精準的,可以通過請求的requestTime/responseTime進行算法計算
// 從服務(wù)器請求最新的時間此迅,簡單示例
func resetServerTime() {
// 從服務(wù)器請求最新的時間
// ...
var success = true
if success {
// 請求成功
serverTimeInterval = 0
} else {
// 如果請求失敗汽畴,隔一段時間再請求一次
perform(#selector(resetServerTime), with: nil, afterDelay: rloadTimeInterval)
}
}