Dispatch_once en Swift: por qué es un error y cómo escribir un singleton correcto
\n\nSi alguna vez te has preguntado «¿Cómo hacer un singleton en Swift usando dispatch_once?» — no estás solo. En Stack Overflow, esta pregunta ha acumulado miles de visitas, y en el código de muchos proyectos aún se encuentran construcciones extrañas que intentan emular el antiguo enfoque de Objective-C. Pero la buena noticia es que en Swift hay una solución mucho más elegante y segura. La mala noticia: muchos desarrolladores siguen cometiendo los mismos errores al intentar trasladar hábitos de Objective-C a Swift.
\n\nEn este artículo analizaremos los 5 principales errores al usar dispatch_once en Swift, mostraremos cómo evitarlos y daremos una plantilla lista para un singleton correcto. ¡Vamos allá!
\n\n¿Qué es dispatch_once y por qué no es necesario en Swift?
\n\ndispatch_once es una función de Grand Central Dispatch (GCD) que garantiza que un bloque de código se ejecute exactamente una vez durante toda la vida de la aplicación. En Objective-C, era la forma estándar de crear singletons seguros para hilos:
\n\n+ (instancetype)sharedInstance {\n static MyClass *sharedInstance = nil;\n static dispatch_once_t onceToken;\n dispatch_once(&onceToken, ^{\n sharedInstance = [[MyClass alloc] init];\n });\n return sharedInstance;\n}\n\nSin embargo, en Swift, desde la versión 1.0, dispatch_once no es compatible para su uso en código de usuario. Sí, puedes importar Dispatch e intentar llamarlo, pero el compilador no lo permitirá. ¿Por qué? Porque Swift ofrece herramientas integradas que hacen que dispatch_once sea innecesario.
Top 5 errores al usar dispatch_once en Swift
\n\nError 1: Intentar llamar a dispatch_once directamente
\n\nEl error más común es intentar escribir algo como:
\n\nclass MySingleton {\n static var shared: MySingleton = {\n let instance = MySingleton()\n // configuración\n return instance\n }()\n \n private init() {}\n}\n\nA primera vista, esto funciona. Pero esto no es dispatch_once. Es simplemente una variable global que se inicializa de forma perezosa (lazy) al primer acceso. Swift garantiza la seguridad para hilos en estas propiedades estáticas, pero no es lo mismo que dispatch_once.
\n\n¿Por qué es malo? Porque pierdes el control sobre el momento de la inicialización. Si tu singleton depende de recursos externos (por ejemplo, base de datos o red), es posible que aún no estén listos.
\n\nError 2: Usar dispatch_once a través de un envoltorio en Objective-C
\n\nAlgunos desarrolladores crean un puente en Objective-C para llamar a dispatch_once desde Swift:
\n\n// Archivo Objective-C\n@interface OnceHelper : NSObject\n+ (void)performOnce:(void(^)(void))block;\n@end\n\n@implementation OnceHelper\nstatic dispatch_once_t onceToken;\n+ (void)performOnce:(void(^)(void))block {\n dispatch_once(&onceToken, block);\n}\n@end\n\nLuego en Swift:
\n\nclass MySingleton {\n static var shared: MySingleton = {\n var instance: MySingleton!\n OnceHelper.performOnce {\n instance = MySingleton()\n }\n return instance\n }()\n}\n\n¿Cuál es el problema? Primero, es un parche. Segundo, dispatch_once_t es una variable global, y si olvidas restablecer el token (por ejemplo, durante pruebas), el singleton no se recreará. Tercero, Apple recomienda explícitamente no usar dispatch_once en Swift.
\n\nError 3: Olvidar la seguridad para hilos
\n\nAlgunos escriben el singleton así:
\n\nclass MySingleton {\n static var shared: MySingleton?\n \n private init() {}\n \n static func getInstance() -> MySingleton {\n if shared == nil {\n shared = MySingleton()\n }\n return shared!\n }\n}