How to wait a certain time and make only the last function call

Let's say I have a class called Person , with variables like firstName and lastName . I listen to changes in these variables using Cocoa's reactive structure, but let me say that I only use the built-in KVO listening, for example, didSet{} . So suppose I have this code:

 let firstName:String { didSet{ self.nameDidChange() }} let lastName: String { didSet{ self.nameDidChange() }} func nameDidChange(){ print("New name:", firstName, lastName} 

Each time I change the first or last name, it automatically calls the nameDidChange function. I am wondering if there are any smart steps to prevent calling the nameDidChange function twice in a row when I change both firstName and lastName .

Let's say the value in firstName is "Anders" and lastName is "Andersson" , then I run this code:

 firstName = "Borat" lastName = "Boratsson" 

nameDidChange will be called here twice. First he prints "New name: Borat Andersson" , then "New name: Borat Boratsson" .

In my simple mind, I think I can create a function called something like nameIsChanging() , call it whenever any of didSet , and start the timer for 0.1 seconds, and then call nameDidChange() but both of these didSet will also call nameIsChanging , so the timer will go twice and fire both times. To solve this problem, I could save the β€œglobal” Timer and invalidate it and restart the account or something like that, but the more I think about the solutions, the uglier they are. Are there "best practices" here?

+5
source share
3 answers

I think you're on the right track. I think you just need to hold up the call to change the name until the user β€œtypes” the command.

Something like that:

 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) } 

Each time the first or middle name changes, he stops the timer and waits another 0.2 seconds until he changes the name.

Edit

After reading Adam Venturella's comment, I realized that this is indeed a debouncing method. It would be useful to use this concept if you want to know a little more about it.

Here is a simple playground that illustrates the concept:

 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() } 

Output:

 changed 

The nameDidChange function was executed only once.

+1
source

Not sure if I understood your question correctly, but instead of timers, you can try using Date to find out when they were fired right after each other or not. Also note that .timeIntervalSince1970 returns the number of seconds since 1970, so I multiplied it by 100 to get the best accuracy.

 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 } 

EDIT: Although this is not performed on the last call, but rather on the first call, but maybe this can help / fix some ideas.

0
source

An easy way to combine calls to nameDidChange() is to call it through DispatchQueue.main.async if that call is no longer being made.

 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 } 

If a single UI event (for example, a touch) results in several changes to firstName and lastName , nameDidChange() will be called only once.

0
source

Source: https://habr.com/ru/post/1273178/


All Articles