2017-11-07 38 views
5

Załóżmy, że mam klasę o nazwie Person, ze zmiennymi takimi jak firstName i lastName. Słucham zmian w tych zmiennych przy użyciu frameworka reactiveCocoa, ale załóżmy, że używam tylko wbudowanego słuchania KVO, jak na przykład didSet{}. Zakładamy więc mam ten kod:Jak czekać na dany czas i wykonać tylko ostatnie wywołanie funkcji

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

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

każdym razem chciałbym zmienić albo imię lub nazwisko, to automatycznie wywołać funkcję nameDidChange. Zastanawiam się, czy jest tu jakiś sprytny ruch, aby zapobiec wywołaniu funkcji nameDidChange dwa razy z rzędu, gdy zmienię zarówno firstName, jak i lastName.

Powiedzmy wartość w firstName jest "Anders" i lastName jest "Andersson", a następnie uruchomić ten kod:

firstName = "Borat" 
lastName = "Boratsson" 

nameDidChange zostanie wywołana dwukrotnie tutaj. Najpierw wydrukuje się "New name: Borat Andersson", a następnie "New name: Borat Boratsson".

W moim prostym umyśle, myślę, że mogę stworzyć funkcję o nazwie coś nameIsChanging(), nazywają go, gdy którykolwiek z didSet nazywa, i uruchomić stoper dla jak 0,1 sekundy, a następniewezwanie nameDidChange(), ale oba z tych didSet s również będą wywoływać nameIsChanging, więc timer przejdzie dwa razy, i wystrzeli oba razy. Aby rozwiązać ten problem, mogłem zachować "globalny" Timer i unieważnić go i zrestartować licznik lub coś podobnego, ale im więcej myślę o rozwiązaniach, tym brzydsze dostają. Czy są tutaj jakieś "najlepsze praktyki"?

+0

Możesz dodać Bool, 'isChangingName', który jest domyślnie 'false' i ustawić w ten sposób w' nameDidChange' wtedy 'nameIsChanging' może użyć' guard' dla upewnienia się, że jest to 'false', ustaw' na 'true ', a następnie uruchom opóźnioną operację za pomocą timera lub' perform (, with:, afterDelay:) ' – theMikeSwan

+0

Myślę, że powinieneś pamiętać, że dodając sztuczne opóźnienie, użytkownik może zinterpretować to jako" wolne "lub odłączone. Czy naprawdę istnieje potrzeba opóźnienia wywoływania funkcji? Jeśli wybierzesz trasę opóźnienia, powinna pojawić się * pewna * wizualna wskazówka, że ​​aplikacja coś robi. – Zig

+0

@Zig ​​Potrzebuję tylko opóźnienia 0,01 sekundy, aby temu zapobiec, ponieważ będą one ustawione w tym samym czasie. Jest to możliwe tylko wtedy, gdy są ustawione programowo w tym samym czasie. Poza tym, potrzebuję ich, aby zadzwonili do funkcji, jeśli są również osobno zmienione. Ma to uniemożliwić każdemu z nich wysłanie żądania adresu URL, gdy nie jest ono potrzebne. – Sti

Odpowiedz

1

Myślę, że jesteś na dobrej drodze. Myślę, że wystarczy opóźnić wywołanie nazwy, aż do momentu, gdy użytkownik zakończy pisanie.

coś takiego:

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

Everytime pierwsza lub druga nazwa jest zmieniana będzie zatrzymać stoper i czekać kolejne 0,2 sekundy, dopóki nie dopuszcza się zmianę nazwy.

Edit

Po przeczytaniu komentarza Adama Venturella zdałem sobie sprawę, że to jest rzeczywiście technika debouncing. Przydałaby się koncepcja google, jeśli chcesz dowiedzieć się więcej na ten temat.

Oto prosty plac zabaw, który ilustruje koncepcję:

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

Wyjście:

changed 

Funkcja nameDidChange została wykonana tylko raz.

+1

Umieszczając nazwę twarzy, można powiedzieć, ta technika nazywa się "wyłamywaniem" –

0

Nie jestem pewien, czy dobrze zrozumiałem twoje pytanie, ale zamiast zegarów możesz spróbować użyć Date, aby sprawdzić, czy zostały one zwolnione tuż po sobie, czy też nie. Zauważ również, że .timeIntervalSince1970 zwraca liczbę sekund od 1970 roku, więc pomnożyłem ją przez 100, aby uzyskać lepszą precyzję.

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: Chociaż to nie działa na ostatniej rozmowy, ale raczej pierwszego połączenia, ale może to może pomóc/iskra kilka pomysłów

0

Łatwy sposób łączyć wywołań nameDidChange() to nazwać poprzez DispatchQueue.main.async, chyba że takie połączenie jest już w toku.

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 
} 

Jeśli pojedyncze zdarzenie UI (na przykład dotykowy) skutkować wieloma zmianami firstName i lastName, nameDidChange() zostanie wywołana tylko raz.