Faça algo a cada x minutos no Swift

113

Como posso executar uma função a cada minuto? Em JavaScript, posso fazer algo como setInterval, existe algo semelhante no Swift?

Saída desejada:

Hello World uma vez por minuto ...

JOSEFtw
fonte
Atualizado para Swift 2: Swift Timer
JamesG

Respostas:

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

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

Lembre-se de importar a Fundação.

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
fonte
1
mas e se você quiser alternar entre as visualizações? O código vai parar certo?
Cing de
5
@Cing depende do que está se referindo.
Antzi,
1
Não se esqueça de que NSTimerretém seu destino, então, com essa configuração, se helloWorldTimeré uma propriedade selfvocê tem um ciclo de retenção, onde selfretém helloWorldTimere helloWorldTimerretém self.
MANIAK_dobrii
@MANIAK_dobrii Você também pode comentar como quebrar o ciclo de retenção? Se o VC for dispensado, mas o cronômetro não cancelado, o ciclo ainda está intacto? Portanto, você deve cancelar o cronômetro e, em seguida, descartar a visualização para interromper o ciclo?
Dave G
1
Para Swift 4, o método do qual você deseja obter o seletor deve ser exposto ao Objective-C, portanto, o atributo @objc deve ser adicionado à declaração do método. por exemplo, `` `@objc func sayHello () {}` ``
Shamim Hossain
136

Se tiver como objetivo a versão 10 e superior do iOS, você pode usar a renderização baseada em bloco de Timer, que simplifica os ciclos de referência fortes em potencial, por exemplo:

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

Embora Timerseja geralmente melhor, para fins de integridade, devo observar que você também pode usar o temporizador de despacho, que é útil para agendar temporizadores em threads de fundo. Com temporizadores de despacho, uma vez que são baseados em blocos, evita alguns dos desafios do ciclo de referência forte com o antigo target/ selectorpadrão de Timer, desde que você use weakreferências.

Assim:

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

Para mais informações, consulte o a um temporizador Criando secção de Exemplos dispatch Fonte no despacho Fontes seção do Guia de Programação Concorrência.


Para Swift 2, consulte a revisão anterior desta resposta .

Roubar
fonte
como você escolheria um cronômetro de execução apenas uma vez dentro dessa abordagem?
Juan Boero
@JuanPabloBoero - Eu não usaria essa abordagem para situações de incêndio único. Você usaria dispatch_after. Ou uma não repetição NSTimer.
Rob
@Rob Eu tenho um rótulo de contador na tela que é atualizado a cada segundo de uma função e estou usando o código acima para incrementar meu contador e atualizar o texto do rótulo. Porém, o problema que estou enfrentando é que minha variável de contador é atualizada a cada segundo, mas a tela não mostra o valor de contador mais recente em meu UILabel.
Nagendra Rao
Rao, o descrito acima é útil para realizar alguma ação em um thread de segundo plano. Mas as atualizações da interface do usuário devem acontecer na fila principal. Portanto, agende isso no thread principal ou pelo menos despache as atualizações da IU de volta para o thread principal.
Rob
1
Esta questão não pertence aqui nos comentários a esta resposta. Exclua seus comentários acima e poste sua própria pergunta. Mas, em resumo, geralmente você não mantém o aplicativo em execução em segundo plano, apenas faz com que pareça que estava. Consulte stackoverflow.com/a/31642036/1271826 .
Rob
17

Aqui está uma atualização para a NSTimerresposta, para Swift 3 (no qual NSTimerfoi renomeado para Timer) usando um encerramento em vez de uma função nomeada:

var timer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true) {
    (_) in
    print("Hello world")
}
John T
fonte
6
isso é apenas para iOS 10.
Glenn de
por favor, não poste soluções ios 10+
Numan Karaaslan
13

Se você pode permitir algum desvio de tempo, aqui está uma solução simples executando algum código a cada minuto:

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

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

Basta executar executeRepeatedly()uma vez e ele será executado a cada minuto. A execução para quando o objeto proprietário ( self) é liberado. Você também pode usar um sinalizador para indicar que a execução deve parar.

Algrid
fonte
Essa decisão é muito mais conveniente do que ter em torno de todos esses temporizadores, adicionando-os aos runloops, invalidando-os ... Legal!
julia_v
parece uma solução válida, mas está criando um ciclo de retenção quando estou usando com minha chamada de serviço da web ...
jayant rawat
11

Você pode usar Timer(swift 3)

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

Em selector () você coloca o nome da sua função

Bas
fonte
Como você pode fazer isso no swift 3?
bibscy
1
usar Timer... NSTimerfoi renomeado
Joseph
7

No swift 3.0, o GCD foi refatorado:

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

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

Isso é especialmente útil quando você precisa despachar em uma fila específica. Além disso, se você está planejando usar isso para atualização da interface do usuário, sugiro olhar CADisplayLinkcomo está sincronizado com a taxa de atualização da GPU.

lata
fonte