Introdução: por que o desempenho é importante desde o primeiro dia
Quando você começa a programar em Swift, é fácil focar apenas na funcionalidade: "só precisa funcionar". No entanto, desenvolvedores experientes sabem: otimização de desempenho não é um toque final, mas uma base que é construída desde as primeiras linhas de código. Um aplicativo lento perde usuários, drena a bateria e recebe avaliações negativas.
Neste artigo, vamos abordar os principais aspectos da otimização de aplicativos Swift para iOS e macOS. Você aprenderá como evitar erros comuns de iniciantes, trabalhar com memória, threads e profiling. Cada seção contém exemplos práticos de código que você pode aplicar imediatamente em seus projetos.
1. Gerenciamento de memória: ARC e como evitar retain cycles
Swift usa Automatic Reference Counting (ARC) para gerenciar memória. Isso significa que os objetos são liberados quando não há mais referências fortes para eles. Mas se você criar uma referência cíclica (retain cycle), a memória nunca será liberada — ocorrerá um vazamento.
Referências fracas e não proprietárias
Use weak e unowned para quebrar os ciclos. Isso é mais frequentemente necessário em closures e delegates.
class ViewController: UIViewController { var networkManager: NetworkManager? func fetchData() { networkManager?.fetch { [weak self] result in guard let self = self else { return } // Processamento do resultado self.updateUI(with: result) } }}Quando usar:
weak— se a referência pode se tornarnil(por exemplo, delegate).unowned— se você tem certeza de que o objeto viverá mais que a closure (por exemplo, controlador pai).
Evite cópias desnecessárias
Value types (structs, enums) são copiados na atribuição. Para dados grandes, use class ou passe por referência com inout.
// Ruim: cópia de um array grandefunc processLargeArray(_ array: [Int]) { // array é uma cópia}
// Bom: passagem por referênciafunc processLargeArray(_ array: inout [Int]) { // array é uma referência ao original}2. Trabalhando com multithreading: GCD e async/await
iOS e macOS são sistemas multithread. Se você executa operações pesadas (carregamento de dados, processamento de imagens) na thread principal, a interface "congela". Use Grand Central Dispatch (GCD) ou o moderno async/await.
GCD: padrões básicos
DispatchQueue.global(qos: .userInitiated).async { // Trabalho pesado em segundo plano let result = performHeavyCalculation() DispatchQueue.main.async { // Atualização da UI na thread principal self.label.text = "\\(result)" }}QoS (Quality of Service):
.userInteractive— para UI (quase instantâneo)..userInitiated— para ações do usuário (segundos)..utility— para tarefas longas (barras de progresso)..background— para sincronização em segundo plano (não urgente).
Async/await (Swift 5.5+)
Uma forma mais legível de trabalhar com assincronia:
func fetchImage(from url: URL) async throws -> UIImage { let (data, _) = try await URLSession.shared.data(from: url) guard let image = UIImage(data: data) else { throw URLError(.badServerResponse) } return image}
// Chamada em TaskTask { do { let image = try await fetchImage(from: url) self.imageView.image = image } catch { print("Erro ao carregar: \\(er")
```