Introdução: por que otimizar aplicativos Swift?
O desenvolvimento para iOS e macOS exige não apenas funcionalidade, mas também alto desempenho. Os usuários esperam animações suaves, inicialização rápida e consumo mínimo de bateria. Neste artigo, abordaremos métodos práticos de otimização de aplicativos Swift que ajudarão você a melhorar a experiência do usuário e evitar erros comuns.
Otimização não é sobre micro-otimização prematura. É sobre escolhas conscientes de arquitetura, estruturas de dados e algoritmos. Vamos analisar quatro aspectos principais: profiling, gerenciamento de memória, multithreading e manipulação de dados.
1. Profiling: encontrando gargalos
Antes de otimizar qualquer coisa, é preciso entender o que está causando lentidão. O Xcode oferece uma ferramenta poderosa — Instruments. Os principais rastreadores para Swift são:
- Time Profiler — mostra quais funções consomem mais CPU.
- Allocations — rastreia alocação de memória e vazamentos.
- Leaks — procura vazamentos de memória.
Exemplo de inicialização de profiling a partir do código:
import os.signpost
let log = OSLog(subsystem: "com.example.app", category: "performance")let signpostID = OSSignpostID(log: log)
os_signpost(.begin, log: log, name: "HeavyComputation", signpostID: signpostID)// Seu códigoos_signpost(.end, log: log, name: "HeavyComputation", signpostID: signpostID)Use signpost para marcar seções críticas. Isso permitirá medir com precisão o tempo de execução no Instruments.
2. Gerenciamento de memória: ARC e ciclos de referência forte
Swift usa Automatic Reference Counting (ARC), mas isso não isenta o desenvolvedor do controle. O problema mais comum são os ciclos de referência forte (retain cycles). Eles causam vazamentos de memória.
2.1. Uso de weak e unowned
Em closures e delegates, sempre use weak ou unowned:
class NetworkManager { var onComplete: (() -> Void)? func fetchData() { onComplete = { [weak self] in guard let self = self else { return } self.updateUI() } } func updateUI() { /* ... */ }}Regra: se o objeto pode se tornar nil — use weak; se existir garantidamente — use unowned.
2.2. Evite cópias excessivas
Tipos de valor (structs, arrays) são copiados ao serem passados. Para coleções grandes, use inout ou NSMutableArray:
// Ruim: cópia do arrayfunc processLargeArray(_ array: [Int]) { var mutable = array // cópia mutable.append(1)}
// Bom: passagem por referênciafunc processLargeArray(_ array: inout [Int]) { array.append(1)}Evite também criar instâncias desnecessárias de String em loops — use NSString ou Data.
3. Multithreading: GCD e Swift Concurrency
Aplicativos modernos devem usar eficientemente processadores multi-core. O Swift oferece duas abordagens: GCD (Grand Central Dispatch) e o novo modelo async/await.
3.1. GCD: uso correto de filas
Não bloqueie a thread principal. Execute todas as tarefas pesadas em filas de fundo:
DispatchQueue.global(qos: .userInitiated).async { let result = self.heavyComputation() DispatchQueue.main.async { self.updateUI(with: result) }}É importante escolher QoS (Quality of Service) de forma significativa:
.userInteractive— para atualizações de UI..userInitiated— para tarefas iniciadas pelo usuário..background— para sincronização de dados.