如何等待给定时间并仅执行最后一次函数调用

时间:2017-11-07 17:07:45

标签: ios swift key-value-observing

假设我有一个名为Person的班级,其中包含firstNamelastName等变量。我正在使用reactiveCocoa框架来监听这些变量的变化,但是假设我只使用内置的KVO监听,就像didSet{}一样。所以假设我有这个代码:

let firstName:String { didSet{ self.nameDidChange() }}
let lastName: String { didSet{ self.nameDidChange() }}

func nameDidChange(){ print("New name:", firstName, lastName}

每当我更改名字或姓氏时,它都会自动调用函数nameDidChange。 我想知道的是,当我更改nameDidChangefirstName时,是否有任何明智的举动可以防止连续两次调用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,并使其自身无效并重新启动计数或类似的东西,但我越想到解决方案,他们就会得到更多的丑陋。这里有“最佳实践”吗?

3 个答案:

答案 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事件(例如触摸)导致对firstNamelastName进行多项更改,则nameDidChange()只会被调用一次。