Rób coś co x minut w Swift

113

Jak mogę uruchamiać funkcję co minutę? W JavaScript mogę zrobić coś takiego setInterval, czy coś podobnego istnieje w Swift?

Poszukiwany wynik:

Witaj świecie raz na minutę ...

JOSEFtw
źródło
Zaktualizowano dla Swift 2: Swift Timer
JamesG,

Odpowiedzi:

171
var helloWorldTimer = NSTimer.scheduledTimerWithTimeInterval(60.0, target: self, selector: Selector("sayHello"), userInfo: nil, repeats: true)

func sayHello() 
{
    NSLog("hello World")
}

Pamiętaj, aby zaimportować Foundation.

Swift 4:

 var helloWorldTimer = Timer.scheduledTimer(timeInterval: 60.0, target: self, selector: #selector(ViewController.sayHello), userInfo: nil, repeats: true)

 @objc func sayHello() 
 {
     NSLog("hello World")
 }
Unheilig
źródło
1
ale co jeśli chcesz przełączać się między widokami? Kod się zatrzyma, prawda?
Cing
5
@Cing zależy od tego, do czego się odnosi.
Antzi
1
Nie zapominaj, że NSTimerzachowuje swój cel, więc przy tej konfiguracji, jeśli helloWorldTimerjest to właściwość self, masz cykl utrzymania, w którym selfzachowuje helloWorldTimeri helloWorldTimerzachowuje self.
MANIAK_dobrii
@MANIAK_dobrii Czy możesz również skomentować, jak przerwać cykl przechowywania? Jeśli VC zostanie odrzucony, ale licznik czasu nie zostanie anulowany, cykl nadal działa? Więc musicie oboje anulować licznik czasu, a następnie odrzucić widok, aby przerwać cykl?
Dave G
1
W przypadku Swift 4 metoda, której chcesz uzyskać selektor, musi być uwidoczniona w Objective-C, dlatego atrybut @objc musi zostać dodany do deklaracji metody. np. `` @objc func sayHello () {} ''
Shamim Hossain
136

Jeśli celujesz w system iOS w wersji 10 lub nowszej, możesz użyć wersji opartej na blokach Timer, co upraszcza potencjalne silne cykle odwołań, np .:

weak var timer: Timer?

func startTimer() {
    timer?.invalidate()   // just in case you had existing `Timer`, `invalidate` it before we lose our reference to it
    timer = Timer.scheduledTimer(withTimeInterval: 60.0, repeats: true) { [weak self] _ in
        // do something here
    }
}

func stopTimer() {
    timer?.invalidate()
}

// if appropriate, make sure to stop your timer in `deinit`

deinit {
    stopTimer()
}

Chociaż Timerogólnie jest to najlepsze, ze względu na kompletność należy zauważyć, że można również użyć licznika czasu wysyłki, który jest przydatny do planowania liczników czasu w wątkach w tle. Dzięki zegarom wysyłkowym, ponieważ są one oparte na blokach, unika się niektórych silnych wyzwań cyklu referencyjnego ze starym wzorcem target/ , o ile używasz odwołań.selectorTimerweak

Więc:

var timer: DispatchSourceTimer?

func startTimer() {
    let queue = DispatchQueue(label: "com.domain.app.timer")  // you can also use `DispatchQueue.main`, if you want
    timer = DispatchSource.makeTimerSource(queue: queue)
    timer!.schedule(deadline: .now(), repeating: .seconds(60))
    timer!.setEventHandler { [weak self] in
        // do whatever you want here
    }
    timer!.resume()
}

func stopTimer() {
    timer?.cancel()
    timer = nil
}

deinit {
    self.stopTimer()
}

Aby uzyskać więcej informacji, zobacz The Tworzenie timera odcinek Wysłanie źródłowe przykładów w dyspozytorskie Sources sekcji Programowanie Współbieżnym Guide.


W przypadku Swift 2 zobacz poprzednią wersję tej odpowiedzi .

Obrabować
źródło
w jaki sposób w ramach tego podejścia zastosowałbyś licznik czasu wykonywania tylko raz?
Juan Boero
@JuanPabloBoero - Nie użyłbym tego podejścia do sytuacji jednorazowego pożaru. Używałbyś dispatch_after. Lub niepowtarzalny NSTimer.
Rob
@Rob Mam na ekranie etykietę licznika, która jest aktualizowana co sekundę z funkcji i używam powyższego kodu do zwiększania licznika i aktualizowania tekstu etykiety. Ale problem, z którym się zmagam, polega na tym, że moja zmienna licznika jest aktualizowana co sekundę, ale ekran nie pokazuje najnowszej wartości licznika w moim UILabel.
Nagendra Rao
Rao, powyższe jest przydatne do wykonywania pewnych czynności w wątku w tle. Ale aktualizacje interfejsu użytkownika muszą odbywać się w głównej kolejce. Więc zaplanuj to w głównym wątku lub przynajmniej wyślij aktualizacje interfejsu użytkownika z powrotem do głównego wątku.
Rob
1
To pytanie nie należy tutaj w komentarzach do tej odpowiedzi. Usuń swoje komentarze powyżej i zadaj własne pytanie. Krótko mówiąc, generalnie nie utrzymujesz aplikacji działającej w tle, a jedynie sprawiasz, że wygląda tak, jakby była. Zobacz stackoverflow.com/a/31642036/1271826 .
Rob
17

Oto aktualizacja NSTimerodpowiedzi dla języka Swift 3 (w którym NSTimerzmieniono nazwę na Timer) przy użyciu zamknięcia zamiast nazwanej funkcji:

var timer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true) {
    (_) in
    print("Hello world")
}
John T.
źródło
6
to tylko dla iOS 10.
Glenn,
proszę nie publikować rozwiązań ios 10+
Numan Karaaslan
13

Jeśli możesz pozwolić sobie na dryf w czasie, oto proste rozwiązanie wykonujące jakiś kod co minutę:

private func executeRepeatedly() {
    // put your code here

    DispatchQueue.main.asyncAfter(deadline: .now() + 60.0) { [weak self] in
        self?.executeRepeatedly()
    }
}

Po prostu uruchom executeRepeatedly()raz i będzie wykonywane co minutę. Wykonywanie zatrzymuje się po selfzwolnieniu obiektu będącego właścicielem ( ). Możesz również użyć flagi, aby wskazać, że wykonanie musi zostać zatrzymane.

algrid
źródło
Ta decyzja jest o wiele wygodniejsza niż posiadanie wszystkich tych liczników czasu, dodawanie ich do pętli, unieważnianie ich… Super!
julia_v
wydaje się, że jest to poprawne rozwiązanie, ale tworzy cykl przechowywania, gdy używam go z moim wywołaniem usługi sieciowej ...
jayant rawat
11

Możesz użyć Timer(Swift 3)

var timer = Timer.scheduledTimerWithTimeInterval(60, target: self, selector: Selector("function"), userInfo: nil, repeats: true)

W selector () podajesz nazwę swojej funkcji

Bas
źródło
Jak możesz to zrobić w Swift 3?
bibscy
1
use Timer... NSTimerzostał przemianowany
Joseph
7

W swift 3.0 GCD został refaktoryzowany:

let timer : DispatchSourceTimer = DispatchSource.makeTimerSource(flags: [], queue: DispatchQueue.main)

timer.scheduleRepeating(deadline: .now(), interval: .seconds(60))
timer.setEventHandler
{
    NSLog("Hello World")
}
timer.resume()

Jest to szczególnie przydatne, gdy chcesz wysłać wiadomość w określonej kolejce. Ponadto, jeśli planujesz używać tego do aktualizacji interfejsu użytkownika, sugeruję sprawdzenie, CADisplayLinkczy jest zsynchronizowany z częstotliwością odświeżania GPU.

Mogą
źródło