Singleton — qué es, una única instancia de clase en iOS y Android

Autor: IT Sectr Publicado: 2026-02-17 Tiempo de lectura: 8 min

Singleton — un patrón de creación que garantiza una única instancia de clase y proporciona un punto de acceso global a ella. Singleton se usa ampliamente en el desarrollo móvil para recursos compartidos: clientes de red, bases de datos, gestores de configuración. El patrón está descrito en el libro clásico de GoF (1994) y sigue siendo uno de los más reconocibles. Más información en Refactoring Guru: Singleton.

Puntos clave

  • Singleton — garantiza una instancia de clase por aplicación
  • Punto de acceso global — propiedad estática shared o companion object
  • Thread safety — se requiere sincronización para funcionar correctamente en entornos multiproceso
  • Crítica — Singleton complica las pruebas y crea dependencias ocultas
  • Alternativas — Dependency Injection, Service Locator para reemplazar Singleton

¿Qué es Singleton: la esencia del patrón singleton?

Singleton — un patrón de diseño de creación descrito por GoF (Gang of Four) en 1994. El patrón resuelve dos problemas: restringe la creación de instancias de una clase a un solo objeto y proporciona acceso global a ese objeto. Singleton es útil para recursos que deben ser únicos: fábricas de sesiones, cachés de imágenes, gestores de conexión a bases de datos, clientes Crashlytics o Analytics.

Implementación de Singleton requiere un constructor privado (prohíbe la creación externa), un campo estático con la única instancia y un método de acceso estático (shared, instance, getInstance). Los clientes llaman a Singleton.shared.method() sin preocuparse por la creación del objeto. El patrón es popular en iOS y Android: URLSession.shared, UserDefaults.standard, FirebaseApp.sharedInstance — todo son Singletons. Sin embargo, el uso excesivo de Singleton lleva al antipatrón Global State.

Problemas de Singleton — dependencias ocultas (las clases dependen implícitamente del objeto Singleton), complejidad en las pruebas (no se puede reemplazar la instancia en pruebas sin esfuerzo adicional), violación del Principio de Responsabilidad Única (Singleton gestiona tanto su instancia como la lógica de negocio). El desarrollo móvil moderno prefiere DI (Dagger, Hilt, Swinject) para gestionar instancias únicas — el contenedor DI crea el objeto una vez y lo inyecta a través del constructor.

Singleton en iOS con Swift: shared y propiedades estáticas

Swift Singleton se implementa mediante una propiedad estática shared con un inicializador privado. Desde Swift 3, la inicialización diferida de propiedades estáticas está garantizada como segura para subprocesos — el compilador añade automáticamente sincronización mediante dispatch_once. Basta con declarar static let shared = Class() y hacer init() privado. Swift no requiere sincronización adicional para acceso de un solo subproceso después de la inicialización.

swift
final class NetworkManager {
    // Singleton seguro para subprocesos
    static let shared = NetworkManager()

    private init() {
        URLSessionConfiguration.default.timeoutIntervalForRequest = 30
    }

    private var cache = NSCache<NSString, NSData>()

    func fetchData(from url: URL) async throws -> Data {
        let key = url.absoluteString as NSString
        if let cached = cache.object(forKey: key) {
            return cached as Data
        }
        let (data, _) = try await URLSession.shared.data(from: url)
        cache.setObject(data as NSData, forKey: key)
        return data
    }
}

// Uso
let data = try await NetworkManager.shared.fetchData(from: url)

Apple Singleton — muchos objetos del SDK de iOS usan Singleton: UIApplication.shared, UIScreen.main, FileManager.default, NotificationCenter.default, UserDefaults.standard. Apple usa Singleton para servicios que son físicamente únicos (una pantalla, una aplicación). Los desarrolladores copian este patrón para sus propios servicios. En SwiftUI, el acceso global a Singleton se reemplaza por Environment y @EnvironmentObject, mejorando la capacidad de prueba.

Singleton en Android con Kotlin: companion object y object

Kotlin Singleton — la forma más simple: la palabra clave object declara una clase singleton con inicialización diferida al primer acceso. Kotlin object es seguro para subprocesos y no requiere sincronización adicional. Si se necesita un Singleton con parámetros de constructor, se usa un companion object con un delegado lazy. En Android, Singleton es a menudo necesario para el contexto de Application y servicios que se inicializan mediante Application.onCreate().

kotlin
// Opción 1: object — Singleton simple sin parámetros
object AppPreferences {
    private val prefs = Application.instance
        .getSharedPreferences("app", Context.MODE_PRIVATE)

    var isFirstLaunch: Boolean
        get() = prefs.getBoolean("first_launch", true)
        set(value) = prefs.edit { putBoolean("first_launch", value) }
}

// Opción 2: companion object — Singleton con parámetros
class ApiClient private constructor(baseUrl: String) {
    companion object {
        @Volatile
        private var instance: ApiClient? = null

        fun getInstance(baseUrl: String): ApiClient {
            return instance ?: this.synchronized {
                instance ?: ApiClient(baseUrl).also { instance = it }
            }
        }
    }

    fun request(endpoint: String): String { /* ... */ }
}

Singleton del SDK de Android — muchos servicios del sistema Android implementan Singleton: context.getSystemService(), Room.databaseBuilder(), Retrofit.Builder(). Los ejemplos incluyen SharedPreferences, MediaPlayer, AudioManager. En aplicaciones Android, Singleton se usa a menudo para repositorios, gestores y fábricas. Google recomienda reemplazar Singleton con DI (Hilt, Koin), donde el ámbito Singleton (Scope.Singleton o @Singleton) es gestionado por el contenedor mientras la clase sigue siendo comprobable.

Thread safety: dispatch_once, synchronized y lock

Thread safety — un requisito crítico para Singleton en entornos multiproceso. Sin sincronización, dos subprocesos pueden comprobar simultáneamente instance == null y crear dos instancias. La solución es bloquear durante la primera creación y liberar después de la inicialización. En Swift, las propiedades estáticas (static let) son seguras para subprocesos por defecto. En Kotlin, object es seguro para subprocesos. Para el estilo Java en Kotlin, se usa synchronized o @Volatile + double-check locking.

LenguajeMecanismoSeguridad para subprocesosInicialización diferida
Swiftstatic letdispatch_once (automático)Sí, al primer acceso
Kotlin objectDeclaración de objectEl inicializador de clase es seguro para subprocesosSí, al primer acceso
Kotlin companionsynchronized + @VolatileDouble-checked lockingSí, mediante lazy o synchronized
Javasynchronized + volatileDouble-checked lockingSí, en getInstance()

Double-checked locking — un patrón para la inicialización diferida de Singleton. Primera comprobación sin sincronización (rápida si la instancia ya existe), segunda dentro de synchronized (creación por un solo subproceso). @Volatile garantiza la visibilidad de los cambios para todos los subprocesos. Sin volatile, otro subproceso podría ver un objeto parcialmente construido. En Kotlin, el delegado lazy con LazyThreadSafetyMode.SYNCHRONIZED implementa automáticamente double-checked locking.

Singleton vs Dependency Injection: cuándo usar

Dependency Injection — una alternativa a Singleton para gestionar instancias únicas. Un contenedor DI (Dagger, Hilt, Koin, Swinject) crea el objeto una vez en un ámbito Singleton y lo inyecta a través del constructor. La clase no sabe sobre su estado Singleton — el contenedor decide. El código se vuelve comprobable: el módulo DI se reemplaza por un módulo mock en las pruebas. Ventajas de DI: dependencias explícitas en el constructor, capacidad de sobrescritura, ciclo de vida unificado.

Cuándo está justificado Singleton — objetos a nivel de sistema: Crashlytics, Analytics, Logging. Estos servicios se inicializan una vez en AppDelegate/Application y se usan en todas partes. DI es excesivo para ellos. Singleton también es conveniente para cachés de imágenes (NSCache, Coil, Glide), donde el acceso global está justificado por el rendimiento. Para todo lo demás, DI es preferible: hace visibles las dependencias, simplifica las pruebas y la refactorización.

Enfoque híbrido — Singleton con capacidad de sobrescritura para pruebas. En Swift, un protocolo + propiedad estática que las pruebas pueden reemplazar (por ejemplo, mediante URLProtocol para URLSession). En Kotlin, una clase abierta con una propiedad inyectable, donde las pruebas establecen un mock mediante reflexión o un setter. Este enfoque mantiene la simplicidad de Singleton pero proporciona capacidades de prueba. Google recomienda Hilt para Android, Apple no impone DI para iOS — la elección depende del equipo.

Preguntas frecuentes

¿Es Singleton un antipatrón?

No, Singleton es un patrón GoF, pero su uso incorrecto frecuente lo convierte en el antipatrón Global State. Singleton está justificado para recursos físicamente únicos (pantalla, impresora, sistema de archivos). Los problemas surgen cuando Singleton se usa para la gestión de datos: dependencias ocultas, complejidad en las pruebas, violación del Principio de Responsabilidad Única. Una alternativa moderna es DI con ámbito Singleton.

¿Cómo probar código que usa Singleton?

Tres enfoques: (1) mediante protocolo — Singleton implementa un protocolo, las pruebas intercambian la implementación; (2) mediante DI — Singleton se inyecta como dependencia a través del constructor; (3) mediante método reset — Singleton tiene un método para restablecer el estado en pruebas (solo para builds de prueba). El primer enfoque es preferible, el tercero es peligroso para producción. Swift permite reemplazar la propiedad shared mediante manipulación en tiempo de ejecución en pruebas.

¿En qué se diferencia Kotlin object de Java Singleton?

Kotlin object es una construcción del lenguaje que crea un Singleton a nivel de bytecode. A diferencia de la implementación de Java con constructor privado y getInstance(), object garantiza seguridad para subprocesos, inicialización diferida y prohíbe la herencia. Java Singleton requiere sincronización manual (synchronized) y volatile para funcionar correctamente en entornos multiproceso. Kotlin object es la forma más segura y concisa en Android.

¿Se puede heredar Singleton?

Heredar Singleton rompe el patrón: si una clase Singleton se puede heredar, una subclase podría crear una segunda instancia, violando la unicidad. En Swift, final class prohíbe la herencia. Kotlin object no se puede heredar (object es sealed). Si se necesita un Singleton con variabilidad, use un contenedor DI con ámbito Singleton: garantiza una única instancia y soporta herencia a través de interfaces.

¿Cómo pasar parámetros a un Singleton en Android?

Los parámetros se pasan mediante init(context: Application) o getInstance(param). Kotlin object no acepta parámetros — use un companion object con un método factoría getInstance(param). Hilt resuelve el problema: @Singleton + @Inject constructor(context: Application) — el contenedor DI inyecta el contexto de Application automáticamente. Para un cliente Retrofit, los parámetros (baseUrl, interceptors) se pasan mediante un builder en el módulo DI.

Resumen

  • Singleton — un patrón con una única instancia y acceso global
  • Swift shared — static let con seguridad para subprocesos garantizada por el compilador
  • Kotlin object — inicialización diferida sin código adicional
  • Thread safety — double-checked locking para Java, automático para Swift/Kotlin
  • SDK de Apple — UIApplication.shared, UserDefaults.standard, FileManager.default
  • SDK de Android — Retrofit, Room, SharedPreferences mediante gestores Singleton
  • Alternativas — Dependency Injection para código comprobable

Desarrollaremos una aplicación móvil llave en mano

IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.

Discutir el proyecto

Lea también