Descomplicando CompletionHandlers em Swift
Em Swift, operações assíncronas são permitidas e isso é uma boa prática, sendo muitas vezes fundamental para a execução do aplicativo, no caso de existirem ações que ocorrem em threads de segundo plano (background), mas que precisam alterar a UI ou retornar valores utilizados na mainThread.
Essas threads são linhas de execução de um processo, onde todas as instruções são processadas. Imagine-as como uma esteira de produção em uma fábrica. A mainThread seria, nesse caso, a esteira principal, onde a interface é carregada. Existem formas de “produzir” em mais esteiras, ou seja, dividir processos em diferentes threads.
O mais comum é usarmos funções assíncronas, marcadas com async, que têm o papel de quebrar a linearidade da execução do código e retornar uma informação somente quando determinada ação for finalizada, ou seja, quando já houver uma resposta.
Porém existem casos específicos de quebra de linearidade que declarar uma função assíncrona não é possível, ou precisamos de alguma informação que necessita apenas de uma espera.
Como por exemplo no caso de uma closure com query ao HealthKit, declarar a função que utiliza a informação dessa query como async é impossível. Então para casos assim a solução é utilizar CompletionHandler.
Mas o que isso significa CompletionHandler, em tradução livre significaria manipulador de conclusão e para facilitar o entendimento é exatamente isso que essa função nativa swift faz, manipula a conclusão de qualquer função e qual será a continuação do fluxo depois que for determinado o fim.
Get Filipi Romão’s stories in your inbox
Join Medium for free to get updates from this writer.
Para facilitar o entendimento da sintaxe, esse é um código exemplo criado pelo ChatGPT, com o objetivo de simular e exemplificar de forma simples a atuação dos completionHandlers. No momento de declarar um completionHandler é utilizado a função completion: @escaping (String) -> Void, passada como parâmetro a uma função.
func carregarDados(completion: @escaping (String) -> Void) {
print("⏳ Iniciando...")
// Simula uma espera de 3 segundos
DispatchQueue.global().asyncAfter(deadline: .now() + 3) {
let resultado = "✅ Dados carregados com sucesso!"
completion(resultado) // chama o completion depois da espera
}
}
carregarDados { mensagem in
print(mensagem) // Só vai rodar depois da pausa de 3 segundos
}Abaixo estão exemplos reais de uma declaração de função que realiza um fetch dos dados do HealthKit e calcula a média de batimentos cardíacos encontrados em determinado período. Nessa função, é usado completionHandler para que o valor só seja retornado após o cálculo estar completo:
func queryFrequenciaCardiaca(workout: HKWorkout,
healthStore: HKHealthStore,
completionHandler: @escaping (Double) -> Void){}var sumFrequency: Double = 0
// Verifica se o tipo de dado para frequência cardíaca (heartRate) está disponível no HealthKit
if let heartRateType = HKQuantityType.quantityType(forIdentifier: .heartRate) {
//Filtra a frequencia de batimento atraves do workout, ou seja so buscará a frequencia pelo workout informado
let hrPredicate = HKQuery.predicateForObjects(from: workout)
//Realiza a query buscando pelo tipo de dado do batimento, usando o filtro de ser só por treino e é sem limite
let hrQuery = HKSampleQuery(sampleType: heartRateType, predicate: hrPredicate, limit: HKObjectQueryNoLimit, sortDescriptors: nil) { _, samples, error in
//Verifica se hrSamples recebe um array da HKQuantity, que é uma frequencia de BPM, mas durante o treino sao registrados varias BPM, entao precisa receber um array desses BPM
var mediumFrequency: Double = 0
if let hrSamples = samples as? [HKQuantitySample], !hrSamples.isEmpty {
//Percorre todas os valores dos BPM
for sample in hrSamples {
//Variavel que armazena cada BPM, formatando para valor double e pega so as frequencias por minuto
let bpm = sample.quantity.doubleValue(for: HKUnit.count().unitDivided(by: .minute()))
//Salva na variavel soma todos os bpm
sumFrequency += bpm
}
// Calcula a média dividindo pelo total de amostras
mediumFrequency = sumFrequency/Double(hrSamples.count)
completionHandler(mediumFrequency)
}
}
//Executa a query do BPM
healthStore.execute(hrQuery)
}O papel do completion handler fica claro aqui. O que acontece é que, sem ele, o valor de mediumFrequency nunca seria levado para fora da closure da query do HealthKit, porque, pela ordem de execução natural, a query seria feita, a linha var mediumFrequency: Double = 0 declararia mediumFrequency como 0 e esse cálculo ocorreria dentro da closure, mas não chegaria para fora a tempo de acompanhar a execução da função e ser usado.
O código abaixo foca no cálculo da soma de todas as frequências registradas, com o objetivo de encontrar a média da frequência cardíaca:
if let hrSamples = samples as? [HKQuantitySample], !hrSamples.isEmpty {
//Percorre todas os valores dos BPM
for sample in hrSamples {
//Variavel que armazena cada BPM, formatando para valor double e pega so as frequencias por minuto
let bpm = sample.quantity.doubleValue(for: HKUnit.count().unitDivided(by: .minute()))
//Salva na variavel soma todos os bpm
sumFrequency += bpm
}
// Calcula a média dividindo pelo total de amostras
mediumFrequency = sumFrequency/Double(hrSamples.count)
completionHandler(mediumFrequency)Se não fosse a linha completionHandler(mediumFrequency), o código “passaria direto”: o valor seria lido dentro da closure do if let bpm, mas a execução não esperaria todo o cálculo dos dados buscados acontecer. Apenas seguiria para o fim da função, e o valor permaneceria zero, porque, no contexto em que a função queryFrequenciaCardiaca é chamada, os valores precisam ser utilizados logo após ela chegar ao fim da execução. Então, caso não fosse definido um ponto onde a execução deve esperar para completar os valores, ela seria inútil.
Mas, no caso da função, todo o escopo dela é continuado quando o cálculo de mediumFrequency é completado, e agora ele pode ser lido fora da closure em que foi calculado.
Resumindo: completionHandlers são uma forma de manipular o que já foi concluído e determinar quando o próximo bloco poderá ser executado, servindo como um alerta de que algo terminou e o código pode continuar.








