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

How to wait for a given time and only perform last function call

假设我有一个名为 Person 的 class,变量如 firstNamelastName。我正在使用 reactiveCocoa 框架监听这些变量的变化,但假设我只使用内置的 KVO 监听,比如 didSet{}。所以假设我有这个代码:

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

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

每次我更改名字或姓氏时,它都会自动调用函数 nameDidChange。 我想知道的是,当我同时更改 firstNamelastName.[=33 时,这里是否有任何明智的举动可以防止 nameDidChange 函数被连续调用两次=]

假设 firstName 中的值是 "Anders"lastName"Andersson",那么我 运行 这段代码:

firstName = "Borat"
lastName = "Boratsson"

nameDidChange这里会被调用两次。它会先打印出"New name: Borat Andersson",然后是"New name: Borat Boratsson".

在我简单的想法中,我想我可以创建一个名为 nameIsChanging() 的函数,每当 didSet 中的任何一个被调用时调用它,并启动一个 0.1 秒的计时器, 并且 then 调用 nameDidChange(), 但是这两个 didSets 也会调用 nameIsChanging, 所以定时器会走两次, 并且触发两次.为了解决这个问题,我可以保留一个 "global" Timer,并让它自己失效并重新开始计数或类似的东西,但我越想解决方案,它们就越难看。这里有"best practices"吗?

我认为您的方向是正确的。我认为您只需要延迟对 name changed 的​​调用,直到用户 "Stopped" 输入。

像这样:

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的评论后,我意识到这确实是一种去抖动技术。如果您想进一步了解它,google 这个概念会很有用。

这是一个简单的 Playground 来说明这个概念:

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 函数只执行了一次。

不确定我是否正确理解了您的问题,但您可以尝试利用 Date 来查看它们是否在彼此之后被解雇,而不是计时器。另请注意,.timeIntervalSince1970 returns 自 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
}

编辑:虽然这不是 运行 最后一次通话,而是第一次通话,但也许这可能有助于/激发一些想法

合并对 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() 将仅被调用一次。