假设我有一个名为Person
的班级,其中包含firstName
和lastName
等变量。我正在使用reactiveCocoa框架来监听这些变量的变化,但是假设我只使用内置的KVO监听,就像didSet{}
一样。所以假设我有这个代码:
let firstName:String { didSet{ self.nameDidChange() }}
let lastName: String { didSet{ self.nameDidChange() }}
func nameDidChange(){ print("New name:", firstName, lastName}
每当我更改名字或姓氏时,它都会自动调用函数nameDidChange
。
我想知道的是,当我更改nameDidChange
和firstName
时,是否有任何明智的举动可以防止连续两次调用lastName
函数。
假设firstName
中的值为"Anders"
且lastName
为"Andersson"
,那么我运行此代码:
firstName = "Borat"
lastName = "Boratsson"
nameDidChange
将在此处被调用两次。它将首先打印出"New name: Borat Andersson"
,然后打印"New name: Borat Boratsson"
。
在我的简单思想中,我想我可以创建一个名为nameIsChanging()
之类的函数,只要调用任何didSet
就调用它,并启动一个0.1秒的计时器,并且然后调用nameDidChange()
,但这两个didSet
也会调用nameIsChanging
,因此计时器将会两次,并且两次都会触发。为了解决这个问题,我可以保留一个“全局”Timer
,并使其自身无效并重新启动计数或类似的东西,但我越想到解决方案,他们就会得到更多的丑陋。这里有“最佳实践”吗?
答案 0 :(得分:2)
我认为你走在正确的轨道上。我认为您只需要将名称的调用延迟更改,直到用户输入“已停止”。
这样的事情:
var timer = Timer()
var firstName: String = "Clint" {
didSet {
timer.invalidate()
timer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: false, block: { _ in
self.nameDidChange()
})
}
}
var secondName: String = "Eastwood" {
didSet {
timer.invalidate()
timer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: false, block: { _ in
self.nameDidChange()
})
}
}
func nameDidChange() {
print(firstName + secondName)
}
每次更改第一个或第二个名称时,它将停止计时器并再等待0.2秒,直到它提交名称更改为止。
修改强>
阅读Adam Venturella的评论后,我意识到这确实是一种去抖动技术。如果您想了解更多有关它的信息,那么谷歌这个概念会很有用。
这是一个简单的游乐场,它说明了这个概念:
import UIKit
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
var timer: Timer? = nil
func nameDidChange() {
print("changed")
}
func debounce(seconds: TimeInterval, function: @escaping () -> Swift.Void ) {
timer?.invalidate()
timer = Timer.scheduledTimer(withTimeInterval: seconds, repeats: false, block: { _ in
function()
})
}
debounce(seconds: 0.2) { nameDidChange() }
debounce(seconds: 0.2) { nameDidChange() }
debounce(seconds: 0.2) { nameDidChange() }
debounce(seconds: 0.2) { nameDidChange() }
debounce(seconds: 0.2) { nameDidChange() }
debounce(seconds: 0.2) { nameDidChange() }
输出:
changed
nameDidChange函数只执行了一次。
答案 1 :(得分:0)
不确定我是否正确理解了您的问题,但您可以尝试利用Date
来查看他们是否在相互之后被解雇的时间而不是计时器。另请注意,.timeIntervalSince1970
返回自1970年以来的秒数,因此我将其乘以100以获得更好的精度。
var firstName:String! { didSet{ self.nameDidChange() }}
var lastName: String! { didSet{ self.nameDidChange() }}
var currentDate: UInt64 = UInt64((Date().timeIntervalSince1970 * 100)) - 100
func nameDidChange(){
let now = UInt64(Date().timeIntervalSince1970 * 100)
//You can add a higher tolerance here if you wish
if (now == currentDate) {
print("firing in succession")
} else {
print("there was a delay between the two calls")
}
currentDate = now
}
编辑:虽然这不会在最后一次通话中运行,而是第一次通话,但也许这可能有助于/激发一些想法
答案 2 :(得分:0)
合并nameDidChange()
来电的简便方法是通过DispatchQueue.main.async
拨打电话,除非此类电话已暂挂。
class MyObject {
var firstName: String = "" { didSet { self.scheduleNameDidChange() } }
var lastName: String = "" { didSet { self.scheduleNameDidChange() } }
private func scheduleNameDidChange() {
if nameDidChangeIsPending { return }
nameDidChangeIsPending = true
RunLoop.main.perform { self.nameDidChange() }
}
private func nameDidChange() {
nameDidChangeIsPending = false
print("New name:", firstName, lastName)
}
private var nameDidChangeIsPending = false
}
如果单个UI事件(例如触摸)导致对firstName
和lastName
进行多项更改,则nameDidChange()
只会被调用一次。