【问题标题】:How to wait for a given time and only perform last function call如何等待给定时间并仅执行最后一次函数调用
【发布时间】:2017-11-07 17:07:44
【问题描述】:

假设我有一个名为Person 的类,其中包含firstNamelastName 之类的变量。我正在使用响应式Cocoa 框架来监听这些变量的变化,但假设我只使用内置的 KVO 监听,例如 didSet{}。所以假设我有这个代码:

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

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

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

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

firstName = "Borat"
lastName = "Boratsson"

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

在我的简单想法中,我想我可以创建一个名为 nameIsChanging() 之类的函数,在任何 didSet 被调用时调用它,并启动一个 0.1 秒的计时器,然后 调用nameDidChange(),但是这两个didSets 也会调用nameIsChanging,所以定时器会走两次,两次都触发。为了解决这个问题,我可以保留一个“全局”Timer,并使其自身失效并重新开始计数或类似的东西,但我越想解决方案,它们就越难看。这里有什么“最佳实践”吗?

【问题讨论】:

  • 您可以添加一个布尔值isChangingName,即默认为false,并在nameDidChange 中设置该方式,然后nameIsChanging 可以使用guard 来确保它是false,将其设置为true,然后使用定时器或perform( , with: , afterDelay: )开始延迟操作
  • 我认为您应该记住,通过添加人为延迟,用户可能会将其解释为感觉“慢”或断开连接。真的需要延迟调用函数吗?如果你确实走延迟路线,应该有一些视觉指示表明应用正在做某事。
  • @Zig 我只需要 0.01 秒的延迟来防止它,因为它们将同时设置。这仅适用于同时以编程方式设置它们的情况。除此之外,如果它们也被单独更改,我还需要它们调用该函数。这是为了防止他们每个人在不需要时触发 url 请求。

标签: ios swift key-value-observing


【解决方案1】:

我认为你在正确的轨道上。我认为您只需要延迟对名称更改的调用,直到用户“停止”输入。

类似这样的:

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

【讨论】:

  • 可以说是把名字放在脸上,这种技术叫做“去抖动”
【解决方案2】:

不确定我是否正确理解了您的问题,但您可以尝试使用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
}

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-02-12
      • 1970-01-01
      • 2011-08-13
      • 1970-01-01
      • 1970-01-01
      • 2019-04-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多