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.
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.
Odpowiedzi:
Zamiast trybu uśpienia, który zablokuje Twój program, jeśli zostanie wywołany z wątku interfejsu użytkownika, rozważ użycie NSTimer
timera wysyłania.
Ale jeśli naprawdę potrzebujesz opóźnienia w bieżącym wątku:
do {
sleep(4)
}
Korzysta z sleep
funkcji UNIX.
Użycie dispatch_after
bloku 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_after
z 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:
let seconds = 4.0
DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
// Put your code which should be executed with a delay here
}
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
}
.now() + .seconds(4)
podaje błąd:expression type is ambiguous without more context
.now() + .seconds(5)
że jest po prostu.now() + 5
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
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.
Podsumowując
Żadna z czterech metod nie działa wystarczająco dobrze sama z siebie. sleep
wyłą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.
@objc
przed funkcją wywołania zwrotnego dla perform(...)
opcji. Tak jak:@objc func callback() {
Zgadzam się z Palle, że używanie dispatch_after
jest 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
}
DispatchTime
nie 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.
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")
}
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 .
Odpowiedź @nneonneo sugeruje użycie, NSTimer
ale 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 sleep
nie jest zalecane, szczególnie w głównym wątku, ponieważ zatrzymuje on całą pracę nad wątkiem.
Patrz tutaj dla mojego pełniejszą odpowiedź.
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
})
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
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 4) {/*Do stuff*/}
jest prawdopodobnie bardziej poprawne ✌️
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).
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.
to jest najprostsze
delay(0.3, closure: {
// put her any code you want to fire it with delay
button.removeFromSuperview()
})