Sitemap

Descomplicando CompletionHandlers em Swift

5 min readSep 1, 2025

--

Press enter or click to view image in full size

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.

Press enter or click to view image in full size

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.

Press enter or click to view image in full size

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.

--

--