Ainda que a palavra-chave defer tenha sido introduzida no Swift 2.0, ela ainda é pouco usada em projetos. Sua utilização pode ser difícil de entender, mas usar o defer pode melhorar muito seu código em alguns lugares.
O caso mais comum visto por aí é abrir e fechar um contexto dentro de um escopo. A partir do Swift 6.4, defer cada vez mais útil para códigos modernos da concorrência no Swift, pois também pode aguardar limpeza assíncrona quando usado em um contexto async.
Como funciona?
A declaração defer é usada para executar código logo antes de transferir o controle do programa fora do escopo onde a declaração aparece.
func updateImage() {
defer { print("Did update image") }
print("Will update image")
imageView.image = updatedImage
}
// Imprime: // Will update Image // Did update image
Ordem de execução com múltiplas declarações defer
Caso várias declarações apareçam no mesmo escopo, a ordem em que elas são definidas é o inverso da ordem em que são executadas. A última declaração definida é a primeira a ser executada, como demonstrado pelo exemplo abaixo ao imprimir números na ordem lógica.
func printStringNumbers() {
defer { print("1") }
defer { print("2") }
defer { print("3") }
print("4")
}
// Imprime 4, 3, 2, 1
A mesma ordem inversa se aplica quando um corpo de defer contém trabalho assíncrono no Swift 6.4: cada bloco diferido é executado na saída do escopo e o trabalho assíncrono é aguardado antes de continuar (mais sobre isso mais tarde).
CURSO GRATUITO DE EMAIL EM 5 DIAS: O Guia Prático da Concorrência no Swift
Um curso gratuito por email em 5 dias revelando os 5 maiores erros que desenvolvedores iOS cometem com async/await que levam a rejeições na App Store e projetos de migração demorarem meses ao invés de dias (mesmo se você já escreve Swift há anos).
Caso de uso comum
O caso mais comum visto por aí é abrir e fechar um contexto dentro de um escopo, por exemplo quando lidando com o acesso a arquivos. Um FileHandle precisa ser fechado uma vez que o acesso tenha sido concluído. Você pode se beneficiar da declaração defer para garantir que não esqueça de fazer isso.
func writeFile() {
let file: FileHandle? = FileHandle(forReadingAtPath: filepath)
defer { file?.closeFile() }
// Escreva alterações no arquivo
}
Aguardando limpeza assíncrona com defer
O Swift 6.4 implementou SE-0493: Suporte a chamadas assíncronas em corpos de defer, permitindo que você use await dentro de um bloco de defer quando o contexto circundante já é assíncrono.
Antes do Swift 6.4, limpeza que exigia await tinha que ser repetida ao longo de várias saídas ou movida para uma tarefa não estruturada separada. Você pode agora manter a configuração e o desfazimento próximos:
func importArticles() async throws {
let importer = ArticleImporter()
await importer.open()
defer {
await importer.close() // Permitido do Swift 6.4
}
try await importer.importLatestArticles()
}
O corpo de defer ainda é executado ao sair do escopo, assim como declarações sincronas defer. A diferença é que o Swift agora aguarda implicitamente a limpeza assíncrona antes que a função retorne.
Se você está novo nesta sintaxe, comece com meu artigo sobre async/await no Swift ou leia mais sobre concorrência estruturada.
Por que não criar uma Task?
Você pode se perguntar por que não pode simplesmente usar um task como fazia antes do Swift 6.4:
func importArticles() async throws {
let importer = ArticleImporter()
await importer.open()
defer {
Task {
await importer.close()
}
}
try await importer.importLatestArticles()
}
Mesmo que isso compile, inicia trabalho não estruturado e permite importArticles() retornar antes que a limpeza seja concluída. Em outras palavras, você perde a garantia que tornou defer útil no primeiro lugar.
Garantindo resultados
Um uso mais avançado da declaração é ao garantir um valor de resultado para ser retornado em uma callback de conclusão. Isso pode ser muito útil, pois é fácil esquecer de disparar esta callback.
func getData(completion: (_ result: Result<String>) -> Void) {
var result: Result<String>?
defer {
guard let result = result else {
fatalError("Devíamos sempre terminar com um resultado")
}
completion(result)
}
// Gere o resultado..
}
A declaração garante que a callback de conclusão seja executada em todos os momentos e verifica o valor do resultado. Sempre que o valor do resultado é nil, o fatalError é lançado e o aplicativo falha.
Algumas regras para lembrar
Caso use defer no Swift, é bom lembrar das seguintes regras:
- Você só pode usar await dentro de defer quando o contexto circundante suporta concorrência.
- Blocos diferidos ainda são executados na ordem inversa.
- A limpeza assíncrona é aguardada antes de sair do escopo.
- O cancelamento não é suprimido: código dentro defer ainda pode observar task.isCancelled
Em outras palavras, se você está usando async dentro de um bloco defer enquanto o método externo não está marcado como assíncrono, você terá a seguinte mensagem de erro:
Você pode usar async dentro de um bloco defer se o método externo é assíncrono.
Perguntas frequentes sobre defer
Eu publiquei este artigo pela primeira vez em 2018, e coletei muitas perguntas desde então. Aqui estão algumas comuns, principalmente relacionadas às mudanças mais recentes na concorrência no Swift:
- Você pode usar await dentro de defer no Swift?
- Sim, a partir do Swift 6.4, você pode usar await dentro de defer quando o contexto circundante é assíncrono.
- O async defer requer uma nova sintaxe?
- Não. Você ainda escreve defer { await cleanup() }, não defer async { ... }.
- O async defer ignora o cancelamento?
- Não. O código dentro de defer ainda pode observar a tarefa de cancelamento.
Conclusão
Amei usar defer no meu código, mas também encontro isso como um recurso que não é comumente usado. A verdade é que você pode escrever seu código sem usá-lo. No entanto, acredito sinceramente que seu código ficará mais limpo em muitos lugares quando você aproveitar o uso de defer.
Se quiser melhorar ainda mais seus conhecimentos no Swift, confira a página da categoria Swift. Sinta-se à vontade para entrar em contato comigo ou me enviar um tweet no Twitter se tiver dicas adicionais ou feedback.

