Jak utworzyć opóźnienie w Swift?

262

Chcę zatrzymać aplikację w określonym momencie. Innymi słowy, chcę, aby moja aplikacja wykonała kod, ale w pewnym momencie zatrzymaj się na 4 sekundy, a następnie kontynuuj z resztą kodu. W jaki sposób mogę to zrobić?

Używam Swift.

Schuey999
źródło

Odpowiedzi:

270

Zamiast trybu uśpienia, który zablokuje Twój program, jeśli zostanie wywołany z wątku interfejsu użytkownika, rozważ użycie NSTimertimera wysyłania.

Ale jeśli naprawdę potrzebujesz opóźnienia w bieżącym wątku:

do {
    sleep(4)
}

Korzysta z sleepfunkcji UNIX.

nneonneo
źródło
4
sen działa bez importowania darwina, nie jestem pewien, czy to zmiana
Dan Beaulieu
17
usleep () również działa, jeśli potrzebujesz czegoś bardziej precyzyjnego niż rozdzielczość 1 sekundy.
prewett
18
usleep () zajmuje milionowe części sekundy, więc usleep (1000000) będzie spał przez 1 sekundę
Harris,
40
Dziękujemy za skrócenie preambuły „nie rób tego”.
Dan Rosenstark,
2
Używam sleep () do symulacji długotrwałych procesów w tle.
William T. Mallard,
345

Użycie dispatch_afterbloku jest w większości przypadków lepsze niż użycie, sleep(time)ponieważ wątek, w którym wykonywany jest sen, jest blokowany przed wykonywaniem innej pracy. podczas korzystania dispatch_afterz wątku, który jest przetwarzany, nie jest blokowany, dzięki czemu może wykonywać inne prace w międzyczasie.
Jeśli pracujesz nad głównym wątkiem aplikacji, korzystanie z niego sleep(time)jest niekorzystne dla wygody użytkownika aplikacji, ponieważ interfejs użytkownika nie odpowiada w tym czasie.

Wyślij po zaplanowaniu wykonania bloku kodu zamiast zamrażania wątku:

Szybki ≥ 3,0

let seconds = 4.0
DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
    // Put your code which should be executed with a delay here
}

Szybki <3,0

let time = dispatch_time(dispatch_time_t(DISPATCH_TIME_NOW), 4 * Int64(NSEC_PER_SEC))
dispatch_after(time, dispatch_get_main_queue()) {
    // Put your code which should be executed with a delay here
}
Palle
źródło
1
Co z działaniami okresowymi?
qed
1
istnieją możliwości użycia gcd do okresowych zadań, które są opisane w tym artykule z dokumentacji programisty Apple, ale do tego poleciłbym użycie NSTimer. ta odpowiedź pokazuje, jak to zrobić szybko.
Palle,
2
Zaktualizowano kod dla Xcode 8.2.1. Wcześniej .now() + .seconds(4)podaje błąd:expression type is ambiguous without more context
richards
Jak mogę zawiesić funkcję wywoływaną z kolejki? @Palle
Mukul More
14
Nie trzeba już dodawać, .now() + .seconds(5)że jest po prostu.now() + 5
cnzac
45

Porównanie różnych podejść w swift 3.0

1. Spać

Ta metoda nie ma oddzwonienia. Umieść kody bezpośrednio po tym wierszu, aby wykonać je w 4 sekundy. Powstrzyma użytkownika od iteracji z elementami interfejsu użytkownika, takimi jak przycisk testowy, aż do upływu czasu. Chociaż przycisk jest jakby zamrożony, gdy zaczyna się sen, inne elementy, takie jak wskaźnik aktywności, wciąż się obracają bez zamrażania. Nie możesz ponownie uruchomić tej akcji podczas snu.

sleep(4)
print("done")//Do stuff here

wprowadź opis zdjęcia tutaj

2. Wyślij, wykonaj i stoper

Te trzy metody działają podobnie, wszystkie działają w wątku w tle z oddzwanianiem, tylko z inną składnią i nieco innymi funkcjami.

Polecenie Dispatch jest powszechnie używane do uruchamiania czegoś w wątku w tle. Ma funkcję zwrotną jako część wywołania funkcji

DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(4), execute: {
    print("done")
})

Perform jest tak naprawdę uproszczonym timerem. Ustawia timer z opóźnieniem, a następnie uruchamia funkcję selektorem.

perform(#selector(callback), with: nil, afterDelay: 4.0)

func callback() {
    print("done")
}}

I wreszcie, timer zapewnia również możliwość powtarzania oddzwaniania, co w tym przypadku nie jest przydatne

Timer.scheduledTimer(timeInterval: 4, target: self, selector: #selector(callback), userInfo: nil, repeats: false)


func callback() {
    print("done")
}}

W przypadku wszystkich tych trzech metod kliknięcie przycisku w celu ich uruchomienia powoduje, że interfejs użytkownika nie zawiesza się i można kliknąć go ponownie. Ponowne kliknięcie przycisku powoduje ustawienie innego timera i oddzwanianie zostanie uruchomione dwukrotnie.

wprowadź opis zdjęcia tutaj

Podsumowując

Żadna z czterech metod nie działa wystarczająco dobrze sama z siebie. sleepwyłączy interakcję użytkownika, więc ekran „ zawiesza się ” (nie w rzeczywistości) i powoduje złe wrażenia użytkownika. Pozostałe trzy metody nie zawieszają ekranu, ale możesz je uruchomić wiele razy i przez większość czasu chcesz poczekać, aż odzyskasz połączenie, zanim zezwolisz użytkownikowi na ponowne nawiązanie połączenia.

Tak więc lepszym projektem będzie użycie jednej z trzech metod asynchronicznych z blokowaniem ekranu. Gdy użytkownik kliknie przycisk, zakryj cały ekran półprzezroczystym widokiem ze wskaźnikiem aktywności wirowania u góry, informującym użytkownika, że ​​kliknięcie przycisku jest obsługiwane. Następnie usuń widok i wskaźnik w funkcji oddzwaniania, informując użytkownika, że ​​akcja jest odpowiednio obsługiwana itp.

Fangming
źródło
1
Pamiętaj, że w Swift 4, przy wyłączonym wnioskowaniu objc, musisz dodać @objcprzed funkcją wywołania zwrotnego dla perform(...)opcji. Tak jak:@objc func callback() {
leanne
32

Zgadzam się z Palle, że używanie dispatch_afterjest tutaj dobrym wyborem . Ale prawdopodobnie nie lubisz połączeń GCD, ponieważ pisanie ich jest dość denerwujące . Zamiast tego możesz dodać tego poręcznego pomocnika :

public func delay(bySeconds seconds: Double, dispatchLevel: DispatchLevel = .main, closure: @escaping () -> Void) {
    let dispatchTime = DispatchTime.now() + seconds
    dispatchLevel.dispatchQueue.asyncAfter(deadline: dispatchTime, execute: closure)
}

public enum DispatchLevel {
    case main, userInteractive, userInitiated, utility, background
    var dispatchQueue: DispatchQueue {
        switch self {
        case .main:                 return DispatchQueue.main
        case .userInteractive:      return DispatchQueue.global(qos: .userInteractive)
        case .userInitiated:        return DispatchQueue.global(qos: .userInitiated)
        case .utility:              return DispatchQueue.global(qos: .utility)
        case .background:           return DispatchQueue.global(qos: .background)
        }
    }
}

Teraz po prostu opóźniasz kod w wątku w tle, takim jak ten:

delay(bySeconds: 1.5, dispatchLevel: .background) { 
    // delayed code that will run on background thread
}

Opóźnianie kodu w głównym wątku jest jeszcze prostsze:

delay(bySeconds: 1.5) { 
    // delayed code, by default run in main thread
}

Jeśli wolisz Framework, który ma również kilka przydatnych funkcji, sprawdź HandySwift . Możesz dodać go do swojego projektu za pośrednictwem Kartaginy lub Accio, a następnie użyć go dokładnie tak, jak w powyższych przykładach:

import HandySwift    

delay(by: .seconds(1.5)) { 
    // delayed code
}
Jeehut
źródło
To wydaje się bardzo pomocne. Czy masz coś przeciwko aktualizacji w szybkiej wersji 3.0? Dzięki
grahamcracker1234
Zacząłem migrować HandySwift do Swift 3 i planuję wydać nową wersję w przyszłym tygodniu. Potem też zaktualizuję powyższy skrypt. Bądźcie czujni.
Jeehut,
Migracja HandySwift do Swift 3 została zakończona i wydana w wersji 1.3.0. Właśnie zaktualizowałem powyższy kod, aby działał z Swift 3! :)
Jeehut,
1
Dlaczego mnożymy przez NSEC_PER_SEC, a następnie dzielimy przez to ponownie? Czy to nie jest zbędne? (np. Double (Int64 (sekundy * Double (NSEC_PER_SEC))) / Double (NSEC_PER_SEC)) Czy nie możesz po prostu napisać „DispatchTime.now () + Double (sekundy)?
Mark A. Donohoe
1
Dziękuję @MarqueIV, oczywiście masz rację. Właśnie zaktualizowałem kod. Był to po prostu wynik automatycznej konwersji z Xcode 8 i konwersja typu była potrzebna wcześniej, ponieważ DispatchTimenie była dostępna w Swift 2. Tak było let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(seconds * Double(NSEC_PER_SEC)))wcześniej.
Jeehut
24

Możesz to również zrobić za pomocą Swift 3.

Wykonaj funkcję po takim opóźnieniu.

override func viewDidLoad() {
    super.viewDidLoad()

    self.perform(#selector(ClassName.performAction), with: nil, afterDelay: 2.0)
}


     @objc func performAction() {
//This function will perform after 2 seconds
            print("Delayed")
        }
Cyryl
źródło
23

W Swift 4.2 i Xcode 10.1

Masz 4 sposoby na opóźnienie. Spośród tych opcji 1 lepiej jest wywołać lub wykonać funkcję po pewnym czasie. Funkcja sleep () jest najmniej używana.

Opcja 1.

DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
    self.yourFuncHere()
}
//Your function here    
func yourFuncHere() {

}

Opcja 2.

perform(#selector(yourFuncHere2), with: nil, afterDelay: 5.0)

//Your function here  
@objc func yourFuncHere2() {
    print("this is...")
}

Opcja 3.

Timer.scheduledTimer(timeInterval: 5.0, target: self, selector: #selector(yourFuncHere3), userInfo: nil, repeats: false)

//Your function here  
@objc func yourFuncHere3() {

}

Opcja 4.

sleep(5)

Jeśli chcesz wywołać funkcję po pewnym czasie, aby coś wykonać, nie używaj trybu uśpienia .

iOS
źródło
19

NSTimer

Odpowiedź @nneonneo sugeruje użycie, NSTimerale nie pokazuje, jak to zrobić. To jest podstawowa składnia:

let delay = 0.5 // time in seconds
NSTimer.scheduledTimerWithTimeInterval(delay, target: self, selector: #selector(myFunctionName), userInfo: nil, repeats: false)

Oto bardzo prosty projekt pokazujący, jak można go użyć. Po naciśnięciu przycisku uruchamia się timer, który wywoła funkcję po upływie pół sekundy.

import UIKit
class ViewController: UIViewController {

    var timer = NSTimer()
    let delay = 0.5
    
    // start timer when button is tapped
    @IBAction func startTimerButtonTapped(sender: UIButton) {

        // cancel the timer in case the button is tapped multiple times
        timer.invalidate()

        // start the timer
        timer = NSTimer.scheduledTimerWithTimeInterval(delay, target: self, selector: #selector(delayedAction), userInfo: nil, repeats: false)
    }

    // function to be called after the delay
    func delayedAction() {
        print("action has started")
    }
}

Używanie dispatch_time(jak w odpowiedzi Palle ) to kolejna ważna opcja. Jednak trudno jest anulować . Za pomocą NSTimer, aby anulować opóźnione zdarzenie, zanim ono nastąpi, wystarczy zadzwonić

timer.invalidate()

Używanie sleepnie jest zalecane, szczególnie w głównym wątku, ponieważ zatrzymuje on całą pracę nad wątkiem.

Patrz tutaj dla mojego pełniejszą odpowiedź.

Suragch
źródło
7

Wypróbuj następującą implementację w Swift 3.0

func delayWithSeconds(_ seconds: Double, completion: @escaping () -> ()) {
    DispatchQueue.main.asyncAfter(deadline: .now() + seconds) { 
        completion()
    }
}

Stosowanie

delayWithSeconds(1) {
   //Do something
}
Vakas
źródło
7

Możesz utworzyć rozszerzenie, aby łatwo korzystać z funkcji opóźnienia (Składnia: Swift 4.2+)

extension UIViewController {
    func delay(_ delay:Double, closure:@escaping ()->()) {
        DispatchQueue.main.asyncAfter(
            deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: closure)
    }
}

Jak korzystać z UIViewController

self.delay(0.1, closure: {
   //execute code
})
Hardik Thakkar
źródło
6

Jeśli konieczne jest ustawienie opóźnienia krótszego niż sekunda, nie trzeba ustawiać parametru .seconds. Mam nadzieję, że to komuś się przyda.

DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
        // your code hear
})
Booharyna
źródło
5
DispatchQueue.global(qos: .background).async {
    sleep(4)
    print("Active after 4 sec, and doesn't block main")
    DispatchQueue.main.async{
        //do stuff in the main thread here
    }
}
eonista
źródło
4
Działanie DispatchQueue.main.asyncAfter(deadline: .now() + 4) {/*Do stuff*/}jest prawdopodobnie bardziej poprawne ✌️
eonista
5

Jeśli kod działa już w wątku w tle, wstrzymaj wątek, używając tej metody w programie Foundation :Thread.sleep(forTimeInterval:)

Na przykład:

DispatchQueue.global(qos: .userInitiated).async {

    // Code is running in a background thread already so it is safe to sleep
    Thread.sleep(forTimeInterval: 4.0)
}

(Zobacz inne odpowiedzi na sugestie, gdy Twój kod działa w głównym wątku).

Robin Stewart
źródło
3

Aby utworzyć proste opóźnienie czasowe, możesz zaimportować Darwin, a następnie użyć uśpienia (sekund), aby zrobić opóźnienie. Zajmuje to jednak całe sekundy, więc aby uzyskać bardziej precyzyjne pomiary, możesz zaimportować Darwina i użyć usle (milionowych części sekundy) do bardzo precyzyjnych pomiarów. Aby to przetestować, napisałem:

import Darwin
print("This is one.")
sleep(1)
print("This is two.")
usleep(400000)
print("This is three.")

Które wydruki czekają 1 sekundę i drukują, następnie czekają 0,4 sekundy, a następnie drukują. Wszystko działało zgodnie z oczekiwaniami.

Ethan Wrightson
źródło
-3

to jest najprostsze

    delay(0.3, closure: {
        // put her any code you want to fire it with delay
        button.removeFromSuperview()   
    })
taha
źródło
2
To nie jest część Fundacji, to, jak zakładam, zawarte w Cocoapod „HandySwift”. Powinieneś to wyjaśnić w swojej odpowiedzi.
Jan Nash,
Czy swift ma coś, co czeka na coś, co się wydarzy, czy nie?
Saeed Rahmatolahi