Singleton — ett konstruktionsmönster som garanterar en enda instans av en klass och tillhandahåller en global åtkomstpunkt till den. Singleton används ofta inom mobilutveckling för delade resurser: nätverksklienter, databaser, inställningshanterare. Mönstret beskrevs i den klassiska boken GoF (1994) och förblir ett av de mest igenkännbara. Mer — på Refactoring Guru: Singleton.
Huvudsakligt
Singleton — ett konstruktionsmönster som beskrevs av GoF (Gang of Four) 1994. Mönstret löser två uppgifter: begränsar skapandet av klassinstansen till ett objekt och tillhandahåller global åtkomst till detta objekt. Singleton är användbart för resurser som måste vara unika: sessionsfabrik, bildcache, databasanlutningshanterare, Crashlytics eller Analytics-klient.
Implementering av Singleton kräver en privat konstruktor (förbjuder extern skapelse), ett statiskt fält med en enda instans och en statisk åtkomstmetod (shared, instance, getInstance). Klienter anropar Singleton.shared.method() utan att behöva oroa sig för att skapa objektet. Mönstret är populärt i iOS och Android: URLSession.shared, UserDefaults.standard, FirebaseApp.sharedInstance — allt detta är Singleton. Men överdriven användning av Singleton leder till anti-mönstret Global State.
Problem med Singleton — dolda beroenden (klasser är implicit beroende av Singleton-objektet), svårighet att testa (instansen kan inte ersättas i ett test utan extra ansträngning), brott mot Single Responsibility Principle (Singleton hanterar både sin instans och affärslogik). Modern mobilutveckling föredrar DI (Dagger, Hilt, Swinject) för att hantera enskilda instanser — DI-behållaren skapar objektet en gång och injicerar det via konstruktorn.
Swift Singleton implementeras via en statisk shared-egenskap med en privat initierare. Från och med Swift 3 är lat initiering av statiska egenskaper garanterat trådsäker — kompilatorn lägger automatiskt till synkronisering via dispatch_once. Det räcker att deklarera static let shared = Class() och göra init() privat. Swift kräver ingen extra synkronisering för entrådad åtkomst efter initiering.
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
}
}
// Användning
let data = try await NetworkManager.shared.fetchData(from: url)
Apple Singleton — i iOS SDK använder många objekt Singleton: UIApplication.shared, UIScreen.main, FileManager.default, NotificationCenter.default, UserDefaults.standard. Apple använder Singleton för tjänster som är fysiskt unika (en skärm, en app). Utvecklare kopierar detta mönster för sina egna tjänster. I SwiftUI ersätts global åtkomst till Singleton av Environment och @EnvironmentObject, vilket förbättrar testbarheten.
Kotlin Singleton — det enklaste sättet: nyckelordet object deklarerar en singleton-klass med lat initiering vid första åtkomst. Kotlin object är trådsäkert och kräver ingen extra synkronisering. Om en Singleton med konstruktorparametrar behövs, används companion object med lazy-delegat. I Android är Singleton ofta nödvändigt för Application-kontexten och tjänster som initieras via Application.onCreate().
// Alternativ 1: object — enkel Singleton utan parametrar
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) }
}
// Alternativ 2: companion object — Singleton med parametrar
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 — många systemtjänster i Android implementerar Singleton: context.getSystemService(), Room.databaseBuilder(), Retrofit.Builder(). Exempel inkluderar SharedPreferences, MediaPlayer, AudioManager. I Android-appar används Singleton ofta för datalager, hanterare och fabriker. Google rekommenderar att ersätta Singleton med DI (Hilt, Koin), där Singleton-scope (Scope.Singleton eller @Singleton) hanteras av behållaren och klassen förblir testbar.
Thread safety — ett kritiskt krav för Singleton i flertrådad miljö. Utan synkronisering kan två trådar samtidigt kontrollera instance == null och skapa två instanser. Lösning — låsning vid första skapelse och frigöring efter initiering. I Swift är statiska egenskaper (static let) som standard trådsäkra. I Kotlin är object trådsäkert. För Java-stil i Kotlin används synchronized eller @Volatile + double-check locking.
| Språk | Mekanism | Trådsäkerhet | Lat initiering |
|---|---|---|---|
| Swift | static let | dispatch_once (auto) | Ja, vid första åtkomst |
| Kotlin object | Object declaration | Klassinitieraren är trådsäker | Ja, vid första åtkomst |
| Kotlin companion | synchronized + @Volatile | Double-checked locking | Ja, via lazy eller synchronized |
| Java | synchronized + volatile | Double-checked locking | Ja, i getInstance() |
Double-checked locking — mönster för lat initiering av Singleton. Första kontroll utan synkronisering (snabb, om instansen redan finns), andra — inuti synchronized (skapande endast av en tråd). @Volatile garanterar synlighet av ändringar för alla trådar. Utan volatile kan en annan tråd se ett delvis skapat objekt. I Kotlin implementerar lazy-delegaten med LazyThreadSafetyMode.SYNCHRONIZED automatiskt double-checked locking.
Dependency Injection — ett alternativ till Singleton för att hantera en enda instans. DI-behållaren (Dagger, Hilt, Koin, Swinject) skapar objektet en gång i Singleton-scope och injicerar det via konstruktorn. Klassen vet inte om sin Singleton-status — detta avgörs av behållaren. Koden blir testbar: i testet ersätts DI-modulen med en mock-modul. Fördelar med DI: explicita beroenden i konstruktorn, möjlighet att åsidosätta, enhetlig livscykel.
När Singleton är motiverat — objekt på systemnivå: Crashlytics, Analytics, Logging. Dessa tjänster initieras en gång i AppDelegate/Application och används överallt. DI är överdrivet för dem. Singleton är också bekvämt för bildcachar (NSCache, Coil, Glide), där global åtkomst är motiverad av prestanda. För allt annat är DI att föredra: det gör beroenden synliga, förenklar testning och omfaktorering.
Hybridmetod — Singleton med möjlighet till åsidosättande för tester. I Swift används ett protokoll + en statisk egenskap som testet kan ersätta (t.ex. via URLProtocol för URLSession). I Kotlin — en öppen klass med injectable-egenskap, där testet ställer in mock via reflektion eller setter. Detta tillvägagångssätt bevarar Singleton-enkelheten men ger verktyg för testning. Google rekommenderar Hilt för Android, Apple påtvingar inte DI för iOS — valet beror på teamet.
Vanliga frågor
Nej, Singleton är ett GoF-mönster, men dess frekventa felaktiga användning gör det till anti-mönstret Global State. Singleton är motiverat för fysiskt unika resurser (skärm, skrivare, filsystem). Problem uppstår när Singleton används för datahantering: dolda beroenden, svårighet att testa, brott mot Single Responsibility Principle. Modernt alternativ — DI med Singleton-scope.
Tre tillvägagångssätt: (1) via protokoll — Singleton implementerar ett protokoll, tester ersätter implementeringen; (2) via DI — Singleton injiceras som ett beroende via konstruktorn; (3) via reset-metod — Singleton har en metod för att återställa tillstånd i tester (endast för testbyggen). Det första tillvägagångssättet är att föredra, det tredje — farligt för produktion. Swift tillåter att shared-egenskapen ersätts via runtime-manipulationer i tester.
Kotlin object — en språkkonstruktion som skapar Singleton på bytekodnivå. Till skillnad från Java-implementeringen med privat konstruktor och getInstance(), garanterar object trådsäkerhet, lat initiering och arvsförbud. Java Singleton kräver manuell synkronisering (synchronized) och volatile för korrekt funktion i flertrådad miljö. Kotlin object — det säkraste och mest koncisa sättet i Android.
Arv av Singleton bryter mot mönstret: om Singleton-klassen kan ärvas kan underklassen skapa en andra instans, vilket bryter uniciteten. I Swift förbjuder final class arv. Kotlin object kan inte ärvas (object — sealed). Om en Singleton med variation behövs, använd en DI-behållare med Singleton-scope: den garanterar en instans och stödjer arv via gränssnitt.
Parametrar skickas via init(context: Application) eller getInstance(param). Kotlin object accepterar inte parametrar — använd companion object med en fabriksmetod getInstance(param). Hilt löser problemet: @Singleton + @Inject constructor(context: Application) — DI-behållaren injicerar Application-kontexten automatiskt. För retrofit-klienten skickas parametrar (baseUrl, interceptors) via builder i DI-modulen.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också