Principais erros de desenvolvedores Swift iniciantes: nível avançado
Ao passar da escrita de telas simples em SwiftUI para a criação de aplicações multithread complexas com arquitetura personalizada, o número de armadilhas aumenta drasticamente. Muitos desenvolvedores, após dominarem a sintaxe básica do Swift, acreditam estar prontos para produção, mas cometem erros críticos que levam a vazamentos de memória, funcionamento instável e bugs difíceis de detectar. Neste artigo, analisaremos quatro categorias de erros mais comuns entre desenvolvedores de nível intermediário.
1. Vazamentos de memória em closures: não apenas [weak self]
O erro mais popular é usar [weak self] sem entender quando isso é realmente necessário. Muitos desenvolvedores colocam weak self em todos os lugares onde há uma closure, o que leva à destruição prematura de objetos. Ou, ao contrário, esquecem dos ciclos de referência forte em lugares não óbvios.
Erro típico: criar uma referência forte a um objeto dentro de uma closure que esse objeto armazena.
class NetworkManager { var onCompletion: (() -> Void)? func fetchData() { // ERRO: a closure captura self fortemente onCompletion = { self.updateUI() } } func updateUI() { print("UI atualizado") }}Solução correta: usar [weak self] ou [unowned self] dependendo do tempo de vida dos objetos.
class NetworkManager { var onCompletion: (() -> Void)? func fetchData() { onCompletion = { [weak self] in guard let self = self else { return } self.updateUI() } } func updateUI() { print("UI atualizado") }}Nuance avançada: tenha cuidado com [unowned self]. Se o objeto for liberado e a closure for chamada, o aplicativo irá travar. Use unowned apenas quando tiver 100% de certeza de que o objeto sobreviverá à closure.
2. Trabalho incorreto com async/await e atores
Com o surgimento da Structured Concurrency no Swift 5.5, muitos desenvolvedores começaram a usar ativamente async/await, mas esquecem do MainActor e do Sendable. Isso leva a race conditions e à atualização da UI a partir de uma thread em segundo plano.
Erro típico: chamar uma função assíncrona que deve ser executada na thread principal sem especificar explicitamente @MainActor.
class ViewModel: ObservableObject { @Published var data: [String] = [] // ERRO: a função não está marcada com @MainActor func loadData() async { let result = await fetchFromNetwork() // Isso pode ser executado em uma thread em segundo plano! self.data = result } func fetchFromNetwork() async -> [String] { try? await Task.sleep(nanoseconds: 1_000_000_000) return ["Item1", "Item2"] }}Solução correta: usar @MainActor para funções que atualizam a UI.
class ViewModel: ObservableObject { @Published var data: [String] = [] @MainActor // Agora é garantido que será executado na thread principal func loadData() async { let result = await fetchFromNetwork() self.data = result } func fetchFromNetwork() async -> [String] { try? await Task.sleep(nanoseconds: 1_000_000_000) return ["Item1", "Item2"] }}Nuance avançada: não se esqueça de nonisolated e actor. Se você usar atores, todos os métodos públicos são assíncronos por padrão. Isso pode levar a atrasos inesperados se você não planejar o isolamento.