Imagine que você está trabalhando em um aplicativo no qual precisa executar periodicamente algumas ações. É exatamente para isso que o Swift usa a classe
Timer .
O timer é usado para planejar ações no aplicativo. Isso pode ser uma ação única ou um procedimento de repetição.
Neste guia, você aprenderá como o timer funciona no iOS, como isso afeta a capacidade de resposta da interface do usuário, como otimizar o consumo de bateria ao usar um timer e como usar o
CADisplayLink para animação.
Como site de teste, usaremos o aplicativo - um agendador de tarefas primitivo.
Introdução
Faça o download do
projeto de origem. Abra-o no Xcode, observe sua estrutura, compile e execute. Você verá o agendador de tarefas mais simples:

Adicione uma nova tarefa a ela. Toque no ícone +, digite o nome da tarefa, toque em Ok.
As tarefas adicionadas têm um carimbo de data / hora. A nova tarefa que você acabou de criar é marcada com zero segundos. Como você pode ver, esse valor não aumenta.
Cada tarefa pode ser marcada como concluída. Toque na tarefa. O nome da tarefa será riscado e será marcado como concluído.
Crie nosso primeiro temporizador
Vamos criar o cronômetro principal do nosso aplicativo. A classe
Timer , também conhecida como
NSTimer, é uma maneira conveniente de agendar uma ação para um momento específico, único e periódico.
Abra
TaskListViewController.swift e adicione essa variável ao
TaskListViewController :
var timer: Timer?
Em seguida, adicione a extensão lá:
E cole este código na extensão:
@objc func updateTimer() {
Neste método, nós:
- Verifique se há linhas visíveis na tabela de tarefas.
- Chame updateTime para cada célula visível. Este método atualiza o registro de data e hora na célula (consulte TaskTableViewCell.swift ).
Em seguida, adicione este código à extensão:
func createTimer() {
Aqui estamos:
- Verifique se o cronômetro contém uma instância da classe Timer .
- Caso contrário, crie um timer que chame updateTimer () a cada segundo.
Então, precisamos criar um timer assim que o usuário adicionar a primeira tarefa. Adicione
createTimer () no início do método
presentAlertController (_ :) .
Inicie o aplicativo e crie algumas novas tarefas. Você verá que o carimbo de hora para cada tarefa muda a cada segundo.

Adicionar tolerância ao timer
Aumentar o número de temporizadores resulta em pior capacidade de resposta da interface do usuário e mais consumo de bateria. Cada cronômetro tenta executar exatamente no horário que lhe é atribuído, pois, por padrão, sua tolerância é zero.
Adicionar uma tolerância ao timer é uma maneira fácil de reduzir o consumo de energia. Isso permite que o sistema execute uma ação do timer entre o tempo atribuído e o tempo atribuído,
mais o tempo de tolerância - mas nunca antes do intervalo atribuído.
Para temporizadores que são executados apenas uma vez, o valor de tolerância é ignorado.
No método
createTimer () , imediatamente após a atribuição do timer, adicione esta linha:
timer?.tolerance = 0.1
Inicie o aplicativo. Nesse caso específico, o efeito não será óbvio (temos apenas um cronômetro); no entanto, na situação real de vários cronômetros, seus usuários terão uma interface mais responsiva e o aplicativo será mais eficiente em termos de energia.

Temporizadores em segundo plano
Curiosamente, o que acontece com os cronômetros quando um aplicativo entra em segundo plano? Para lidar com isso, adicione esse código no início do método
updateTimer () :
if let fireDateDescription = timer?.fireDate.description { print(fireDateDescription) }
Isso nos permitirá rastrear eventos de timer no console.
Execute o aplicativo, adicione a tarefa. Agora pressione o botão Início no seu dispositivo e retorne ao nosso aplicativo.
No console, você verá algo assim:

Como você pode ver, quando o aplicativo entra em segundo plano, o iOS pausa todos os temporizadores de aplicativos em execução. Quando o aplicativo se torna ativo, o iOS retoma os cronômetros.
Noções básicas sobre loops de execução
Um loop de execução é um loop de eventos que agenda o trabalho e manipula os eventos recebidos. O ciclo mantém o encadeamento ocupado enquanto está em execução e o coloca no estado "inativo" quando não há trabalho para ele.
Cada vez que você inicia o aplicativo, o sistema cria o encadeamento principal do aplicativo, cada encadeamento possui um loop de execução criado automaticamente para ele.
Mas por que todas essas informações são importantes para você agora? Agora todo timer inicia no thread principal e se junta ao loop de execução. Você provavelmente está ciente de que o thread principal está envolvido na renderização da interface do usuário, no processamento de toques e assim por diante. Se o segmento principal estiver ocupado com algo, a interface do seu aplicativo poderá ficar "sem resposta" (travar).
Você notou que o carimbo de data e hora na célula não é atualizado quando você arrasta a exibição da tabela?

Você pode resolver esse problema informando o ciclo de execução para iniciar os temporizadores em um modo diferente.
Noções básicas sobre modos de ciclo de execução
O modo de ciclo de execução é um conjunto de fontes de entrada, como tocar na tela ou clicar no mouse, que pode ser configurado para monitorar e um conjunto de "observadores" que recebem notificações.
Existem três modos de tempo de execução no iOS:
padrão : as fontes de entrada que não são NSConnectionObjects são processadas.
comum : um conjunto de ciclos de entrada está sendo processado, para o qual você pode definir um conjunto de fontes de entrada, temporizadores, "observadores".
rastreamento : a interface do usuário do aplicativo está sendo processada.
Para nossa aplicação, o modo mais adequado é
comum . Para usá-lo, substitua o conteúdo do método
createTimer () pelo seguinte:
if timer == nil { let timer = Timer(timeInterval: 1.0, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true) RunLoop.current.add(timer, forMode: .common) timer.tolerance = 0.1 self.timer = timer }
A principal diferença do código anterior é que, antes de atribuir o cronômetro ao TaskListViewController, adicionamos esse cronômetro ao loop de execução no modo
comum .
Compile e execute o aplicativo.

Agora, os carimbos de data e hora das células são atualizados mesmo se a tabela estiver rolada.
Adicione animação para concluir todas as tarefas
Agora, adicionamos uma animação de felicitações para o usuário concluir todas as tarefas - a bola subirá da parte inferior da tela para o topo.
Adicione essas variáveis no início do TaskListViewController:
O objetivo dessas variáveis é:
- armazenamento temporizador de animação.
- armazenamento do tempo do início e final da animação.
- duração da animação.
- altura da animação.
Agora adicione a seguinte
extensão TaskListViewController no final do arquivo
TaskListViewController.swift :
Aqui fazemos o seguinte:
- calcular a altura da animação, obtendo a altura da tela do dispositivo
- centralize a bola fora da tela e defina sua visibilidade
- atribuir o horário de início e término da animação
- iniciamos o timer de animação e atualizamos a animação 60 vezes por segundo
Agora precisamos criar a lógica real para atualizar a animação de congratulações. Adicione este código após
showCongratulationAnimation () :
func updateAnimation() {
O que fazemos:
- verifique se endTime e startTime estão atribuídos
- economizar tempo atual em constante
- garantimos que a hora final ainda não chegou. Se já chegou, atualize o cronômetro e esconda nossa bola
- calcular a nova coordenada y da bola
- a posição horizontal da bola é calculada em relação à posição anterior
Agora substitua
// TODO: Animation aqui em
showCongratulationAnimation () por este código:
self.updateAnimation()
Agora
updateAnimation () é chamado sempre que ocorre um evento de timer.
Viva, acabamos de criar uma animação. No entanto, quando o aplicativo é iniciado, nada de novo acontece ...
Mostrando animação
Como você provavelmente adivinhou, não há nada para "lançar" nossa nova animação. Para fazer isso, precisamos de outro método. Adicione este código à
extensão de animação TaskListViewController:
func showCongratulationsIfNeeded() { if taskList.filter({ !$0.completed }).count == 0 { showCongratulationAnimation() } }
Vamos chamar esse método sempre que o usuário marcar a tarefa como concluída, ele verifica se todas as tarefas foram concluídas.
Nesse caso, ele chamará
showCongratulationAnimation () .
Em conclusão, adicione uma chamada a esse método no final de
tableView (_: didSelectRowAt :) :
showCongratulationsIfNeeded()
Inicie o aplicativo, crie algumas tarefas, marque-as como concluídas - e você verá nossa animação!

Paramos o cronômetro
Se você olhar para o console, verá que, embora o usuário tenha marcado todas as tarefas como concluídas, o cronômetro continua funcionando. Isso é completamente inútil, por isso faz sentido parar o cronômetro quando não é necessário.
Primeiro, crie um novo método para parar o cronômetro:
func cancelTimer() { timer?.invalidate() timer = nil }
Isso atualizará o cronômetro e redefinirá para zero, para que possamos criá-lo corretamente novamente mais tarde.
invalidate () é a única maneira de remover o Timer do loop de execução. O loop de execução removerá a referência de timer forte imediatamente após a chamada de
invalidate () ou um pouco mais tarde.
Agora substitua o método showCongratulationsIfNeeded () da seguinte maneira:
func showCongratulationsIfNeeded() { if taskList.filter({ !$0.completed }).count == 0 { cancelTimer() showCongratulationAnimation() } else { createTimer() } }
Agora, se o usuário concluir todas as tarefas, o aplicativo primeiro redefinirá o cronômetro e depois mostrará a animação; caso contrário, tentará criar um novo cronômetro se ainda não estiver lá.
Inicie o aplicativo.

Agora o temporizador para e reinicia como deveria.
CADisplayLink para animação suave
O temporizador não é a escolha ideal para controlar a animação. Você pode ter notado alguns quadros pulando animação, especialmente se você executar o aplicativo no simulador.
Ajustamos o timer para 60Hz. Assim, o timer atualiza a animação a cada 16 ms. Considere a situação mais de perto:

Ao usar o
Timer, não sabemos a hora exata em que a ação começou. Isso pode acontecer no início ou no final do quadro. Digamos que o cronômetro funcione no meio de cada quadro (pontos azuis na imagem). A única coisa que sabemos com certeza é que a chamada será a cada 16 ms.
Agora, temos apenas 8 ms para executar a animação, e isso pode não ser suficiente para a nossa animação. Vamos olhar para o segundo quadro na figura. O segundo quadro não pode ser concluído no tempo alocado, portanto, o aplicativo redefinirá o segundo quadro da animação.
O CADisplayLink nos ajudará
O CADisplayLink é chamado uma vez por quadro e tenta sincronizar os quadros de animação reais o máximo possível. Agora você terá todos os 16 ms à sua disposição e o iOS não eliminará um único quadro.
Para usar o
CADisplayLink , você precisa substituir o
animationTimer por um novo tipo.
Substitua este código
var animationTimer: Timer?
neste:
var displayLink: CADisplayLink?
Você substituiu o
Timer pelo
CADisplayLink .
O CADisplayLink é uma exibição de timer vinculada à verificação vertical da tela. Isso significa que a GPU do dispositivo será pausada até que a tela possa continuar processando os comandos da GPU. Dessa forma, obtemos animação suave.
Substitua este código
var startTime: TimeInterval?, endTime: TimeInterval?
neste:
var startTime: CFTimeInterval?, endTime: CFTimeInterval?
Você substituiu
TimeInterval por
CFTimeInterval , necessário para trabalhar com CADisplayLink.
Substitua o texto do método
showCongratulationAnimation () por este:
func showCongratulationAnimation() {
O que estamos fazendo aqui:
- Defina a altura da animação, as coordenadas da bola e a visibilidade - quase da mesma forma que fizeram antes.
- Inicialize startTime com CACurrentMediaTime () (em vez de Date ()).
- Criamos uma instância da classe CADisplayLink e a adicionamos ao loop de execução no modo comum .
Agora substitua
updateAnimation () pelo seguinte código:
- Adicione objc à assinatura do método (para CADisplayLink, o parâmetro seletor requer essa assinatura).
- Substitua a inicialização por Date () para inicializar a data de CoreAnimation .
- Substitua a chamada animationTimer.invalidate () por uma pausa de CADisplayLink e invalide. Isso também removerá o CADisplayLink do loop de execução.
Inicie o aplicativo!

Ótimo! Substituímos com sucesso a animação baseada no
Timer por um
CADisplayLink mais apropriado - e
deixamos a animação mais suave, sem espasmos.
Conclusão
Neste guia, você descobriu como a classe
Timer funciona no iOS, qual é o ciclo de execução e como ele pode tornar seu aplicativo mais responsivo em termos de interface e como usar o
CADisplayLink em vez do Timer para uma animação suave.