Singleton — ein Erzeugungsmuster, das eine einzelne Klasseninstanz garantiert und einen globalen Zugriffspunkt darauf bereitstellt. Singleton wird in der mobilen Entwicklung häufig für gemeinsame Ressourcen verwendet: Netzwerkclients, Datenbanken, Einstellungsmanager. Das Muster wird im klassischen GoF-Buch (1994) beschrieben und ist eines der bekanntesten. Mehr erfahren Sie auf Refactoring Guru: Singleton.
Wichtige Punkte
Singleton — ein von GoF (Gang of Four) 1994 beschriebenes Erzeugungsmuster. Das Muster löst zwei Probleme: Es beschränkt die Instanziierung einer Klasse auf ein einzelnes Objekt und bietet globalen Zugriff auf dieses Objekt. Singleton ist nützlich für Ressourcen, die eindeutig sein müssen: Session-Fabriken, Bild-Caches, Datenbankverbindungsmanager, Crashlytics- oder Analytics-Clients.
Singleton-Implementierung erfordert einen privaten Konstruktor (verhindert externe Erstellung), ein statisches Feld mit der einzigen Instanz und eine statische Zugriffsmethode (shared, instance, getInstance). Clients rufen Singleton.shared.method() auf, ohne sich um die Objekterstellung zu kümmern. Das Muster ist in iOS und Android beliebt: URLSession.shared, UserDefaults.standard, FirebaseApp.sharedInstance — alles Singletons. Übermäßiger Gebrauch von Singleton führt jedoch zum Anti-Pattern Global State.
Singleton-Probleme — versteckte Abhängigkeiten (Klassen hängen implizit vom Singleton-Objekt ab), Testkomplexität (Instanz kann in Tests nicht ohne zusätzlichen Aufwand ersetzt werden), Verletzung des Single Responsibility Principle (Singleton verwaltet sowohl seine Instanz als auch die Geschäftslogik). Moderne mobile Entwicklung bevorzugt DI (Dagger, Hilt, Swinject) zur Verwaltung einzelner Instanzen — der DI-Container erstellt das Objekt einmal und injiziert es über den Konstruktor.
Swift Singleton wird über eine statische shared-Eigenschaft mit einem privaten Initialisierer implementiert. Seit Swift 3 ist die träge Initialisierung statischer Eigenschaften garantiert threadsicher — der Compiler fügt automatisch Synchronisation über dispatch_once hinzu. Es reicht aus, static let shared = Class() zu deklarieren und init() privat zu machen. Swift benötigt keine zusätzliche Synchronisation für Single-Thread-Zugriff nach der Initialisierung.
final class NetworkManager {
// Thread-sicheres 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
}
}
// Verwendung
let data = try await NetworkManager.shared.fetchData(from: url)
Apple Singleton — viele iOS SDK-Objekte verwenden Singleton: UIApplication.shared, UIScreen.main, FileManager.default, NotificationCenter.default, UserDefaults.standard. Apple verwendet Singleton für physisch eindeutige Dienste (ein Bildschirm, eine Anwendung). Entwickler kopieren dieses Muster für ihre eigenen Dienste. In SwiftUI wird der globale Zugriff auf Singleton durch Environment und @EnvironmentObject ersetzt, was die Testbarkeit verbessert.
Kotlin Singleton — der einfachste Weg: Das Schlüsselwort object deklariert eine Singleton-Klasse mit träger Initialisierung beim ersten Zugriff. Kotlin object ist threadsicher und benötigt keine zusätzliche Synchronisation. Wenn ein Singleton mit Konstruktorparametern benötigt wird, wird ein companion object mit einem lazy-Delegaten verwendet. In Android ist Singleton oft für den Application-Kontext und Dienste erforderlich, die über Application.onCreate() initialisiert werden.
// Option 1: object — einfaches Singleton ohne Parameter
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) }
}
// Option 2: companion object — Singleton mit Parametern
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 — viele Android-Systemdienste implementieren Singleton: context.getSystemService(), Room.databaseBuilder(), Retrofit.Builder(). Beispiele umfassen SharedPreferences, MediaPlayer, AudioManager. In Android-Anwendungen wird Singleton oft für Repositories, Manager und Fabriken verwendet. Google empfiehlt, Singleton durch DI (Hilt, Koin) zu ersetzen, wobei der Singleton-Scope (Scope.Singleton oder @Singleton) vom Container verwaltet wird, während die Klasse testbar bleibt.
Thread safety — eine kritische Anforderung für Singleton in Multithread-Umgebungen. Ohne Synchronisation können zwei Threads gleichzeitig instance == null prüfen und zwei Instanzen erstellen. Die Lösung ist Sperren während der ersten Erstellung und Freigabe nach der Initialisierung. In Swift sind statische Eigenschaften (static let) standardmäßig threadsicher. In Kotlin ist object threadsicher. Für Java-Stil in Kotlin wird synchronized oder @Volatile + double-check locking verwendet.
| Sprache | Mechanismus | Threadsicherheit | Träge Initialisierung |
|---|---|---|---|
| Swift | static let | dispatch_once (automatisch) | Ja, beim ersten Zugriff |
| Kotlin object | Object-Deklaration | Klasseninitialisierer threadsicher | Ja, beim ersten Zugriff |
| Kotlin companion | synchronized + @Volatile | Double-checked locking | Ja, über lazy oder synchronized |
| Java | synchronized + volatile | Double-checked locking | Ja, in getInstance() |
Double-checked locking — ein Muster für die träge Singleton-Initialisierung. Erste Prüfung ohne Synchronisation (schnell, wenn die Instanz bereits existiert), zweite innerhalb von synchronized (Erstellung nur durch einen Thread). @Volatile garantiert Sichtbarkeit von Änderungen für alle Threads. Ohne volatile könnte ein anderer Thread ein teilweise erstelltes Objekt sehen. In Kotlin implementiert der lazy-Delegat mit LazyThreadSafetyMode.SYNCHRONIZED automatisch double-checked locking.
Dependency Injection — eine Alternative zu Singleton zur Verwaltung einzelner Instanzen. Ein DI-Container (Dagger, Hilt, Koin, Swinject) erstellt das Objekt einmal in einem Singleton-Scope und injiziert es über den Konstruktor. Die Klasse weiß nichts über ihren Singleton-Status — der Container entscheidet. Der Code wird testbar: Das DI-Modul wird in Tests durch ein Mock-Modul ersetzt. DI-Vorteile: explizite Abhängigkeiten im Konstruktor, Überschreibbarkeit, einheitlicher Lebenszyklus.
Wann Singleton gerechtfertigt ist — Objekte auf Systemebene: Crashlytics, Analytics, Logging. Diese Dienste werden einmal in AppDelegate/Application initialisiert und überall verwendet. DI ist dafür übertrieben. Singleton ist auch praktisch für Bild-Caches (NSCache, Coil, Glide), wo globaler Zugriff durch Leistung gerechtfertigt ist. Für alles andere ist DI vorzuziehen: Es macht Abhängigkeiten sichtbar, vereinfacht Tests und Refactoring.
Hybrider Ansatz — Singleton mit Überschreibbarkeit für Tests. In Swift ein Protokoll + statische Eigenschaft, die Tests ersetzen können (z.B. über URLProtocol für URLSession). In Kotlin eine offene Klasse mit einer injizierbaren Eigenschaft, wo Tests über Reflexion oder einen Setter einen Mock setzen. Dieser Ansatz bewahrt die Einfachheit von Singleton, bietet aber Testmöglichkeiten. Google empfiehlt Hilt für Android, Apple erzwingt DI nicht für iOS — die Wahl hängt vom Team ab.
Häufig gestellte Fragen
Nein, Singleton ist ein GoF-Muster, aber seine häufige falsche Verwendung verwandelt es in das Global State Anti-Pattern. Singleton ist für physisch eindeutige Ressourcen (Bildschirm, Drucker, Dateisystem) gerechtfertigt. Probleme entstehen, wenn Singleton zur Datenverwaltung verwendet wird: versteckte Abhängigkeiten, Testkomplexität, Verletzung des Single Responsibility Principle. Eine moderne Alternative ist DI mit Singleton-Scope.
Drei Ansätze: (1) über Protokoll — Singleton implementiert ein Protokoll, Tests tauschen die Implementierung aus; (2) über DI — Singleton wird als Abhängigkeit über den Konstruktor injiziert; (3) über reset-Methode — Singleton hat eine Methode zum Zurücksetzen des Zustands in Tests (nur für Test-Builds). Der erste Ansatz ist vorzuziehen, der dritte ist gefährlich für die Produktion. Swift erlaubt das Ersetzen der shared-Eigenschaft durch Laufzeitmanipulation in Tests.
Kotlin object ist ein Sprachkonstrukt, das Singleton auf Bytecode-Ebene erstellt. Im Gegensatz zur Java-Implementierung mit privatem Konstruktor und getInstance() garantiert object Threadsicherheit, träge Initialisierung und verbietet Vererbung. Java Singleton erfordert manuelle Synchronisation (synchronized) und volatile für korrekte Arbeit in Multithread-Umgebungen. Kotlin object ist der sicherste und prägnanteste Weg in Android.
Vererbung von Singleton verletzt das Muster: Wenn eine Singleton-Klasse vererbt werden kann, könnte eine Unterklasse eine zweite Instanz erstellen und die Eindeutigkeit verletzen. In Swift verbietet final class die Vererbung. Kotlin object kann nicht vererbt werden (object ist sealed). Wenn ein Singleton mit Variabilität benötigt wird, verwenden Sie einen DI-Container mit Singleton-Scope: Er garantiert eine einzelne Instanz und unterstützt Vererbung über Schnittstellen.
Parameter werden über init(context: Application) oder getInstance(param) übergeben. Kotlin object akzeptiert keine Parameter — verwenden Sie ein companion object mit einer Factory-Methode getInstance(param). Hilt löst das Problem: @Singleton + @Inject constructor(context: Application) — der DI-Container injiziert den Application-Kontext automatisch. Für einen Retrofit-Client werden Parameter (baseUrl, interceptors) über einen Builder im DI-Modul übergeben.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch