Singleton — vytvářecí vzor, který zaručuje jedinou instanci třídy a poskytuje globální přístupový bod k ní. Singleton je široce používán v mobilním vývoji pro sdílené zdroje: síťové klienty, databáze, správce nastavení. Vzor byl popsán v klasické knize GoF (1994) a zůstává jedním z nejznámějších. Více — na Refactoring Guru: Singleton.
Hlavní
Singleton — vytvářecí vzor popsaný GoF (Gang of Four) v roce 1994. Vzor řeší dva úkoly: omezuje vytváření instance třídy na jeden objekt a poskytuje globální přístup k tomuto objektu. Singleton je užitečný pro zdroje, které musí být jedinečné: továrna na relace, mezipaměť obrázků, správce připojení k databázi, klient Crashlytics nebo Analytics.
Implementace Singleton vyžaduje soukromý konstruktor (zakazuje externí vytváření), statické pole s jedinou instancí a statickou přístupovou metodu (shared, instance, getInstance). Klienti volají Singleton.shared.method() bez starosti o vytváření objektu. Vzor je populární v iOS a Android: URLSession.shared, UserDefaults.standard, FirebaseApp.sharedInstance — všechno jsou to Singletony. Nadměrné používání Singleton však vede k anti-vzoru Global State.
Problémy Singleton — skryté závislosti (třídy implicitně závisí na objektu Singleton), obtížnost testování (instanci nelze v testu nahradit bez dalšího úsilí), porušení Single Responsibility Principle (Singleton spravuje jak svou instanci, tak obchodní logiku). Moderní mobilní vývoj preferuje DI (Dagger, Hilt, Swinject) pro správu jediných instancí — DI kontejner vytvoří objekt jednou a vloží ho přes konstruktor.
Swift Singleton je implementován přes statickou vlastnost shared s privátním inicializátorem. Od Swift 3 je líná inicializace statických vlastností zaručeně vláknově bezpečná — kompilátor automaticky přidá synchronizaci přes dispatch_once. Stačí deklarovat static let shared = Class() a nastavit init() jako privátní. Swift nevyžaduje další synchronizaci pro jednovláknový přístup po inicializaci.
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
}
}
// Použití
let data = try await NetworkManager.shared.fetchData(from: url)
Apple Singleton — v iOS SDK mnoho objektů používá Singleton: UIApplication.shared, UIScreen.main, FileManager.default, NotificationCenter.default, UserDefaults.standard. Apple používá Singleton pro služby, které jsou fyzicky jedinečné (jedna obrazovka, jedna aplikace). Vývojáři kopírují tento vzor pro své služby. V SwiftUI je globální přístup k Singleton nahrazen Environment a @EnvironmentObject, což zlepšuje testovatelnost.
Kotlin Singleton — nejjednodušší způsob: klíčové slovo object deklaruje třídu-singleton s línou inicializací při prvním přístupu. Kotlin object je vláknově bezpečný a nevyžaduje další synchronizaci. Pokud je potřeba Singleton s parametry konstruktoru, použije se companion object s lazy delegátem. V Android je Singleton často nezbytný pro kontext Application a služby inicializované přes Application.onCreate().
// Varianta 1: object — jednoduchý Singleton bez parametrů
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) }
}
// Varianta 2: companion object — Singleton s parametry
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 — mnoho systémových služeb Android implementuje Singleton: context.getSystemService(), Room.databaseBuilder(), Retrofit.Builder(). Příklady zahrnují SharedPreferences, MediaPlayer, AudioManager. V Android aplikacích se Singleton často používá pro repozitáře, správce a továrny. Google doporučuje nahradit Singleton DI (Hilt, Koin), kde je Singleton rozsah (Scope.Singleton nebo @Singleton) spravován kontejnerem a třída zůstává testovatelná.
Thread safety — kritický požadavek pro Singleton ve vícevláknovém prostředí. Bez synchronizace mohou dvě vlákna současně zkontrolovat instance == null a vytvořit dvě instance. Řešení — uzamčení při prvním vytvoření a uvolnění po inicializaci. Ve Swift jsou statické vlastnosti (static let) standardně vláknově bezpečné. V Kotlin je object vláknově bezpečný. Pro Java styl v Kotlin se používá synchronized nebo @Volatile + double-check locking.
| Jazyk | Mechanismus | Vláknová bezpečnost | Líná inicializace |
|---|---|---|---|
| Swift | static let | dispatch_once (automatické) | Ano, při prvním přístupu |
| Kotlin object | Object declaration | Inicializátor třídy je vláknově bezpečný | Ano, při prvním přístupu |
| Kotlin companion | synchronized + @Volatile | Double-checked locking | Ano, přes lazy nebo synchronized |
| Java | synchronized + volatile | Double-checked locking | Ano, v getInstance() |
Double-checked locking — vzor pro línou inicializaci Singleton. První kontrola bez synchronizace (rychlá, pokud instance již existuje), druhá — uvnitř synchronized (vytvoření pouze jedním vláknem). @Volatile zaručuje viditelnost změn pro všechna vlákna. Bez volatile může jiné vlákno vidět částečně vytvořený objekt. V Kotlin lazy delegát s LazyThreadSafetyMode.SYNCHRONIZED automaticky implementuje double-checked locking.
Dependency Injection — alternativa k Singleton pro správu jediné instance. DI kontejner (Dagger, Hilt, Koin, Swinject) vytvoří objekt jednou v Singleton rozsahu a vloží ho přes konstruktor. Třída neví o svém Singleton statusu — to rozhoduje kontejner. Kód se stává testovatelným: v testu je DI modul nahrazen mock modulem. Výhody DI: explicitní závislosti v konstruktoru, možnost přepsání, jednotný životní cyklus.
Kdy je Singleton oprávněný — objekty systémové úrovně: Crashlytics, Analytics, Logging. Tyto služby jsou inicializovány jednou v AppDelegate/Application a používány všude. DI je pro ně nadbytečný. Singleton je také vhodný pro mezipaměti obrázků (NSCache, Coil, Glide), kde je globální přístup ospravedlněn výkonem. Pro všechno ostatní je DI preferovanější: činí závislosti viditelnými, zjednodušuje testování a refaktorování.
Hybridní přístup — Singleton s možností přepsání pro testy. Ve Swift se používá protokol + statická vlastnost, kterou může test nahradit (např. přes URLProtocol pro URLSession). V Kotlin — otevřená třída s injectable vlastností, kde test nastaví mock přes reflexi nebo setter. Tento přístup zachovává jednoduchost Singleton, ale poskytuje nástroje pro testování. Google doporučuje Hilt pro Android, Apple nevnucuje DI pro iOS — volba závisí na týmu.
Často kladené dotazy
Ne, Singleton je vzor GoF, ale jeho časté nesprávné použití ho mění na anti-vzor Global State. Singleton je oprávněný pro fyzicky jedinečné zdroje (obrazovka, tiskárna, souborový systém). Problémy vznikají, když je Singleton používán pro správu dat: skryté závislosti, obtížnost testování, porušení Single Responsibility Principle. Moderní alternativa — DI s Singleton rozsahem.
Tři přístupy: (1) přes protokol — Singleton implementuje protokol, testy nahrazují implementaci; (2) přes DI — Singleton je vložen jako závislost přes konstruktor; (3) přes reset metodu — Singleton má metodu pro resetování stavu v testech (pouze pro testovací sestavení). První přístup je preferovaný, třetí — nebezpečný pro produkci. Swift umožňuje nahradit vlastnost shared pomocí runtime manipulací v testech.
Kotlin object — jazyková konstrukce, která vytváří Singleton na úrovni bytekódu. Na rozdíl od Java implementace s privátním konstruktorem a getInstance(), object zaručuje vláknovou bezpečnost, línou inicializaci a zákaz dědičnosti. Java Singleton vyžaduje ruční synchronizaci (synchronized) a volatile pro správnou funkci ve vícevláknovém prostředí. Kotlin object — nejbezpečnější a nejstručnější způsob v Android.
Dědění Singleton porušuje vzor: pokud lze třídu Singleton dědit, podtřída může vytvořit druhou instanci, čímž poruší jedinečnost. Ve Swift final class zakazuje dědičnost. Kotlin object nelze dědit (object — sealed). Pokud je potřeba Singleton s variabilitou, použijte DI kontejner s Singleton rozsahem: zaručuje jednu instanci a podporuje dědičnost přes rozhraní.
Parametry se předávají přes init(context: Application) nebo getInstance(param). Kotlin object nepřijímá parametry — použijte companion object s tovární metodou getInstance(param). Hilt řeší problém: @Singleton + @Inject constructor(context: Application) — DI kontejner vloží kontext Application automaticky. Pro retrofit klienta se parametry (baseUrl, interceptors) předávají přes builder v DI modulu.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také