Singleton — co to jest, pojedyncza instancja klasy w iOS i Android

Autor: IT Sectr Opublikowano: 2026-02-17 Czas czytania: 8 min

Singleton — wzorzec konstrukcyjny, który gwarantuje pojedynczą instancję klasy i zapewnia globalny punkt dostępu do niej. Singleton jest szeroko stosowany w programowaniu mobilnym dla współdzielonych zasobów: klienty sieciowe, bazy danych, menedżery ustawień. Wzorzec został opisany w klasycznej książce GoF (1994) i pozostaje jednym z najbardziej rozpoznawalnych. Więcej — na Refactoring Guru: Singleton.

Najważniejsze

  • Singleton — gwarantuje jedną instancję klasy w całej aplikacji
  • Globalny punkt dostępu — statyczna właściwość shared lub companion object
  • Thread safety — wymagana synchronizacja dla poprawnego działania w środowisku wielowątkowym
  • Krytyka — Singleton utrudnia testowanie i tworzy ukryte zależności
  • Alternatywy — Dependency Injection, Service Locator jako zamiana Singleton

Czym jest Singleton: istota wzorca singleton?

Singleton — wzorzec konstrukcyjny opisany przez GoF (Gang of Four) w 1994 roku. Wzorzec rozwiązuje dwa zadania: ogranicza tworzenie instancji klasy do jednego obiektu i zapewnia globalny dostęp do tego obiektu. Singleton jest przydatny dla zasobów, które muszą być unikalne: fabryka sesji, pamięć podręczna obrazów, menedżer połączeń z bazą danych, klient Crashlytics lub Analytics.

Implementacja Singleton wymaga prywatnego konstruktora (zabrania zewnętrznego tworzenia), statycznego pola z pojedynczą instancją i statycznej metody dostępu (shared, instance, getInstance). Klienci wywołują Singleton.shared.method(), nie martwiąc się o tworzenie obiektu. Wzorzec jest popularny w iOS i Android: URLSession.shared, UserDefaults.standard, FirebaseApp.sharedInstance — wszystko to Singleton. Jednak nadmierne użycie Singleton prowadzi do antywzorca Global State.

Problemy Singleton — ukryte zależności (klasy niejawnie zależą od obiektu Singleton), trudność testowania (nie można zastąpić instancji w teście bez dodatkowego wysiłku), naruszenie Single Responsibility Principle (Singleton zarządza zarówno swoją instancją, jak i logiką biznesową). Współczesne programowanie mobilne preferuje DI (Dagger, Hilt, Swinject) do zarządzania pojedynczymi instancjami — kontener DI tworzy obiekt raz i wstrzykuje go przez konstruktor.

Singleton w iOS na Swift: shared i właściwości statyczne

Swift Singleton jest implementowany przez statyczną właściwość shared z prywatnym inicjalizatorem. Od Swift 3, leniwa inicjalizacja właściwości statycznych jest gwarantowana jako bezpieczna wątkowo — kompilator automatycznie dodaje synchronizację przez dispatch_once. Wystarczy zadeklarować static let shared = Class() i ustawić init() jako prywatny. Swift nie wymaga dodatkowej synchronizacji dla dostępu jednowątkowego po inicjalizacji.

swift
final class NetworkManager {
    // Thread-safe Singleton
    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
    }
}

// Użycie
let data = try await NetworkManager.shared.fetchData(from: url)

Apple Singleton — w iOS SDK wiele obiektów używa Singleton: UIApplication.shared, UIScreen.main, FileManager.default, NotificationCenter.default, UserDefaults.standard. Apple używa Singleton dla usług, które są fizycznie unikalne (jeden ekran, jedna aplikacja). Deweloperzy kopiują ten wzorzec dla swoich usług. W SwiftUI globalny dostęp do Singleton jest zastępowany przez Environment i @EnvironmentObject, co poprawia testowalność.

Singleton w Android na Kotlin: companion object i object

Kotlin Singleton — najprostszy sposób: słowo kluczowe object deklaruje klasę-singleton z leniwą inicjalizacją przy pierwszym dostępie. Kotlin object jest bezpieczny wątkowo i nie wymaga dodatkowej synchronizacji. Jeśli potrzebny jest Singleton z parametrami konstruktora, używany jest companion object z delegatem lazy. W Android Singleton jest często niezbędny dla kontekstu Application i usług inicjalizowanych przez Application.onCreate().

kotlin
// Wariant 1: object — prosty Singleton bez parametrów
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) }
}

// Wariant 2: companion object — Singleton z parametrami
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 { /* ... */ }
}

Android SDK Singleton — wiele systemowych usług Androida implementuje Singleton: context.getSystemService(), Room.databaseBuilder(), Retrofit.Builder(). Przykłady obejmują SharedPreferences, MediaPlayer, AudioManager. W aplikacjach Android Singleton jest często używany dla repozytoriów, menedżerów i fabryk. Google zaleca zastępowanie Singleton na DI (Hilt, Koin), gdzie zakres Singleton (Scope.Singleton lub @Singleton) jest zarządzany przez kontener, a klasa pozostaje testowalna.

Thread safety: dispatch_once, synchronized i lock

Thread safety — krytyczny wymóg dla Singleton w środowisku wielowątkowym. Bez synchronizacji dwa wątki mogą jednocześnie sprawdzić instance == null i utworzyć dwie instancje. Rozwiązanie — blokada przy pierwszym tworzeniu i zwolnienie po inicjalizacji. W Swift właściwości statyczne (static let) są bezpieczne wątkowo domyślnie. W Kotlin object jest bezpieczny wątkowo. Dla stylu Java w Kotlin używany jest synchronized lub @Volatile z double-check locking.

JęzykMechanizmBezpieczeństwo wątkoweLeniwa inicjalizacja
Swiftstatic letdispatch_once (auto)Tak, przy pierwszym dostępie
Kotlin objectObject declarationInicjalizator klasy jest bezpieczny wątkowoTak, przy pierwszym dostępie
Kotlin companionsynchronized + @VolatileDouble-checked lockingTak, przez lazy lub synchronized
Javasynchronized + volatileDouble-checked lockingTak, w getInstance()

Double-checked locking — wzorzec dla leniwej inicjalizacji Singleton. Pierwsze sprawdzenie bez synchronizacji (szybkie, jeśli instancja już istnieje), drugie — wewnątrz synchronized (tworzenie tylko przez jeden wątek). @Volatile gwarantuje widoczność zmian dla wszystkich wątków. Bez volatile inny wątek może zobaczyć częściowo utworzony obiekt. W Kotlin delegat lazy z LazyThreadSafetyMode.SYNCHRONIZED automatycznie implementuje double-checked locking.

Singleton vs Dependency Injection: kiedy używać

Dependency Injection — alternatywa dla Singleton do zarządzania pojedynczą instancją. Kontener DI (Dagger, Hilt, Koin, Swinject) tworzy obiekt raz w zakresie Singleton i wstrzykuje go przez konstruktor. Klasa nie wie o swoim statusie Singleton — decyduje o tym kontener. Kod staje się testowalny: w teście moduł DI jest zastępowany przez moduł mock. Zalety DI: jawne zależności w konstruktorze, możliwość nadpisania, jednolity cykl życia.

Kiedy Singleton jest uzasadniony — obiekty systemowe: Crashlytics, Analytics, Logging. Te usługi są inicjalizowane raz w AppDelegate/Application i używane wszędzie. DI jest dla nich nadmiarowy. Singleton jest również wygodny dla pamięci podręcznych obrazów (NSCache, Coil, Glide), gdzie globalny dostęp jest uzasadniony wydajnością. Dla wszystkiego innego preferowany jest DI: czyni zależności widocznymi, upraszcza testowanie i refaktoryzację.

Podejście hybrydowe — Singleton z możliwością nadpisania dla testów. W Swift używany jest protokół + właściwość statyczna, którą test może zastąpić (np. przez URLProtocol dla URLSession). W Kotlin — open class z właściwością injectable, gdzie test ustawia mock przez refleksję lub setter. Takie podejście zachowuje prostotę Singleton, ale daje narzędzia do testowania. Google zaleca Hilt dla Android, Apple nie narzuca DI dla iOS — wybór zależy od zespołu.

Często zadawane pytania

Singleton — to antywzorzec?

Nie, Singleton to wzorzec GoF, ale jego częste nieprawidłowe użycie przekształca go w antywzorzec Global State. Singleton jest uzasadniony dla fizycznie unikalnych zasobów (ekran, drukarka, system plików). Problemy pojawiają się, gdy Singleton jest używany do zarządzania danymi: ukryte zależności, trudność testowania, naruszenie Single Responsibility Principle. Nowoczesną alternatywą jest DI z zakresem Singleton.

Jak testować kod używający Singleton?

Trzy podejścia: (1) przez protokół — Singleton implementuje protokół, testy zastępują implementację; (2) przez DI — Singleton jest wstrzykiwany jako zależność przez konstruktor; (3) przez metodę reset — Singleton ma metodę do resetowania stanu w testach (tylko dla builda testowego). Pierwsze podejście jest preferowane, trzecie — niebezpieczne dla produkcji. Swift pozwala zastąpić właściwość shared przez manipulacje runtime w testach.

Czym Kotlin object różni się od Java Singleton?

Kotlin object — konstrukcja językowa, która tworzy Singleton na poziomie bajtkodu. W przeciwieństwie do implementacji Java z prywatnym konstruktorem i getInstance(), object gwarantuje bezpieczeństwo wątkowe, leniwą inicjalizację i zakaz dziedziczenia. Java Singleton wymaga ręcznej synchronizacji (synchronized) i volatile dla poprawnego działania w środowisku wielowątkowym. Kotlin object — najbezpieczniejszy i najbardziej zwięzły sposób w Android.

Czy można dziedziczyć Singleton?

Dziedziczenie Singleton narusza wzorzec: jeśli klasa Singleton może być dziedziczona, to podklasa może utworzyć drugą instancję, naruszając unikalność. W Swift final class zabrania dziedziczenia. Kotlin object nie może być dziedziczony (object — sealed). Jeśli potrzebny jest Singleton z wariantami, użyj kontenera DI z zakresem Singleton: gwarantuje on jedną instancję i obsługuje dziedziczenie przez interfejsy.

Jak przekazać parametry do Singleton w Android?

Parametry są przekazywane przez init(context: Application) lub getInstance(param). Kotlin object nie przyjmuje parametrów — użyj companion object z metodą fabryczną getInstance(param). Hilt rozwiązuje problem: @Singleton + @Inject constructor(context: Application) — kontener DI wstrzykuje kontekst Application automatycznie. Dla klienta retrofit parametry (baseUrl, interceptors) są przekazywane przez builder w module DI.

Podsumowanie

  • Singleton — wzorzec z pojedynczą instancją i globalnym dostępem
  • Swift shared — static let z bezpieczeństwem wątkowym od kompilatora
  • Kotlin object — leniwa inicjalizacja bez dodatkowego kodu
  • Thread safety — double-checked locking dla Java, automatyczne dla Swift/Kotlin
  • Apple SDK — UIApplication.shared, UserDefaults.standard, FileManager.default
  • Android SDK — Retrofit, Room, SharedPreferences przez menedżery Singleton
  • Alternatywy — Dependency Injection dla testowalnego kodu

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również