Principales errores de desarrolladores Swift principiantes: nivel avanzado
Cuando pasas de escribir pantallas simples en SwiftUI a crear aplicaciones complejas multiproceso con arquitectura personalizada, la cantidad de trampas aumenta drásticamente. Muchos desarrolladores, después de dominar la sintaxis básica de Swift, se consideran listos para producción, pero cometen errores críticos que provocan fugas de memoria, funcionamiento inestable y errores difíciles de detectar. En este artículo, analizaremos cuatro categorías de errores que se encuentran con mayor frecuencia en desarrolladores de nivel intermedio.
1. Fugas de memoria en closures: no solo [weak self]
El error más común es usar [weak self] sin entender cuándo es realmente necesario. Muchos desarrolladores colocan weak self en todas partes donde hay un closure, lo que provoca la destrucción prematura de objetos. O, por el contrario, olvidan los ciclos de referencia fuertes en lugares no obvios.
Error típico: crear una referencia fuerte a un objeto dentro de un closure que ese objeto almacena.
class NetworkManager { var onCompletion: (() -> Void)? func fetchData() { // ERROR: el closure captura self fuertemente onCompletion = { self.updateUI() } } func updateUI() { print("UI actualizado") }}Solución correcta: usar [weak self] o [unowned self] dependiendo del tiempo de vida de los objetos.
class NetworkManager { var onCompletion: (() -> Void)? func fetchData() { onCompletion = { [weak self] in guard let self = self else { return } self.updateUI() } } func updateUI() { print("UI actualizado") }}Matiz avanzado: ten cuidado con [unowned self]. Si el objeto se libera y se invoca el closure, la aplicación fallará con un crash. Usa unowned solo cuando estés 100% seguro de que el objeto sobrevivirá al closure.
2. Trabajo incorrecto con async/await y actores
Con la llegada de Structured Concurrency en Swift 5.5, muchos desarrolladores comenzaron a usar activamente async/await, pero olvidan MainActor y Sendable. Esto provoca race conditions y actualizaciones de la UI desde un hilo en segundo plano.
Error típico: llamar a una función asíncrona que debería ejecutarse en el hilo principal sin indicar explícitamente @MainActor.
class ViewModel: ObservableObject { @Published var data: [String] = [] // ERROR: la función no está marcada con @MainActor func loadData() async { let result = await fetchFromNetwork() // ¡Esto podría ejecutarse en un hilo en segundo plano! self.data = result } func fetchFromNetwork() async -> [String] { try? await Task.sleep(nanoseconds: 1_000_000_000) return ["Item1", "Item2"] }}Solución correcta: usar @MainActor para funciones que actualizan la UI.
class ViewModel: ObservableObject { @Published var data: [String] = [] @MainActor // Ahora se ejecuta garantizadamente en el hilo 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"] }}Matiz avanzado: no olvides nonisolated y actor. Si usas actores, todos los métodos públicos son asíncronos por defecto. Esto puede provocar retrasos inesperados si no planificas el aislamiento.