Singleton — a creational pattern that guarantees a single class instance and provides a global access point to it. Singleton is widely used in mobile development for shared resources: network clients, databases, settings managers. The pattern is described in the classic GoF book (1994) and remains one of the most recognizable. Learn more at Refactoring Guru: Singleton.
Key Points
Singleton — a creational design pattern described by GoF (Gang of Four) in 1994. The pattern solves two problems: it restricts class instantiation to a single object and provides global access to that object. Singleton is useful for resources that must be unique: session factories, image caches, database connection managers, Crashlytics or Analytics clients.
Singleton implementation requires a private constructor (prevents external creation), a static field with the single instance, and a static access method (shared, instance, getInstance). Clients call Singleton.shared.method() without worrying about object creation. The pattern is popular in iOS and Android: URLSession.shared, UserDefaults.standard, FirebaseApp.sharedInstance — all are Singletons. However, excessive use of Singleton leads to the Global State anti-pattern.
Singleton problems — hidden dependencies (classes implicitly depend on the Singleton object), testing complexity (cannot replace the instance in tests without additional effort), violation of the Single Responsibility Principle (Singleton manages both its instance and business logic). Modern mobile development prefers DI (Dagger, Hilt, Swinject) for managing single instances — the DI container creates the object once and injects it through the constructor.
Swift Singleton is implemented through a static shared property with a private initializer. Since Swift 3, lazy initialization of static properties is guaranteed to be thread-safe — the compiler automatically adds synchronization via dispatch_once. Declaring static let shared = Class() and making init() private is sufficient. Swift does not require additional synchronization for single-threaded access after initialization.
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
}
}
// Usage
let data = try await NetworkManager.shared.fetchData(from: url)
Apple Singleton — many iOS SDK objects use Singleton: UIApplication.shared, UIScreen.main, FileManager.default, NotificationCenter.default, UserDefaults.standard. Apple uses Singleton for services that are physically unique (one screen, one application). Developers copy this pattern for their own services. In SwiftUI, global Singleton access is replaced by Environment and @EnvironmentObject, improving testability.
Kotlin Singleton — the simplest way: the object keyword declares a singleton class with lazy initialization upon first access. Kotlin object is thread-safe and requires no additional synchronization. If a Singleton with constructor parameters is needed, a companion object with a lazy delegate is used. In Android, Singleton is often necessary for Application context and services that initialize through Application.onCreate().
// Option 1: object — simple Singleton without parameters
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 with parameters
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 — many Android system services implement Singleton: context.getSystemService(), Room.databaseBuilder(), Retrofit.Builder(). Examples include SharedPreferences, MediaPlayer, AudioManager. In Android applications, Singleton is often used for repositories, managers, and factories. Google recommends replacing Singleton with DI (Hilt, Koin), where Singleton scope (Scope.Singleton or @Singleton) is managed by the container while the class remains testable.
Thread safety — a critical requirement for Singleton in multithreaded environments. Without synchronization, two threads can simultaneously check instance == null and create two instances. The solution is locking during first creation and releasing after initialization. In Swift, static properties (static let) are thread-safe by default. In Kotlin, object is thread-safe. For Java-style in Kotlin, synchronized or @Volatile + double-check locking is used.
| Language | Mechanism | Thread safety | Lazy initialization |
|---|---|---|---|
| Swift | static let | dispatch_once (automatic) | Yes, on first access |
| Kotlin object | Object declaration | Class initializer is thread-safe | Yes, on first access |
| Kotlin companion | synchronized + @Volatile | Double-checked locking | Yes, via lazy or synchronized |
| Java | synchronized + volatile | Double-checked locking | Yes, in getInstance() |
Double-checked locking — a pattern for lazy Singleton initialization. First check without synchronization (fast if the instance already exists), second inside synchronized (creation by only one thread). @Volatile guarantees visibility of changes for all threads. Without volatile, another thread may see a partially constructed object. In Kotlin, the lazy delegate with LazyThreadSafetyMode.SYNCHRONIZED automatically implements double-checked locking.
Dependency Injection — an alternative to Singleton for managing single instances. A DI container (Dagger, Hilt, Koin, Swinject) creates the object once in a Singleton scope and injects it through the constructor. The class does not know about its Singleton status — the container decides. Code becomes testable: the DI module is replaced with a mock module in tests. DI advantages: explicit dependencies in the constructor, overridability, unified lifecycle.
When Singleton is justified — system-level objects: Crashlytics, Analytics, Logging. These services are initialized once in AppDelegate/Application and used everywhere. DI is overkill for them. Singleton is also convenient for image caches (NSCache, Coil, Glide), where global access is justified by performance. For everything else, DI is preferable: it makes dependencies visible, simplifies testing and refactoring.
Hybrid approach — Singleton with overridability for tests. In Swift, a protocol + static property that tests can replace (e.g., via URLProtocol for URLSession). In Kotlin, an open class with an injectable property, where tests set a mock through reflection or a setter. This approach keeps Singleton simplicity but provides testing capabilities. Google recommends Hilt for Android, Apple does not impose DI for iOS — the choice depends on the team.
Frequently Asked Questions
No, Singleton is a GoF pattern, but its frequent incorrect usage turns it into the Global State anti-pattern. Singleton is justified for physically unique resources (screen, printer, file system). Problems arise when Singleton is used for data management: hidden dependencies, testing complexity, violation of the Single Responsibility Principle. A modern alternative is DI with Singleton scope.
Three approaches: (1) via protocol — Singleton implements a protocol, tests swap the implementation; (2) via DI — Singleton is injected as a dependency through the constructor; (3) via reset method — Singleton has a method to reset state in tests (only for test builds). The first approach is preferable, the third is dangerous for production. Swift allows replacing the shared property through runtime manipulation in tests.
Kotlin object is a language construct that creates a Singleton at bytecode level. Unlike Java implementation with a private constructor and getInstance(), object guarantees thread safety, lazy initialization, and prohibits inheritance. Java Singleton requires manual synchronization (synchronized) and volatile for correct operation in multithreaded environments. Kotlin object is the safest and most concise way in Android.
Inheriting Singleton breaks the pattern: if a Singleton class can be inherited, a subclass could create a second instance, violating uniqueness. In Swift, final class prohibits inheritance. Kotlin object cannot be inherited (object is sealed). If a Singleton with variability is needed, use a DI container with Singleton scope: it guarantees a single instance and supports inheritance through interfaces.
Parameters are passed via init(context: Application) or getInstance(param). Kotlin object does not accept parameters — use a companion object with a factory method getInstance(param). Hilt solves the problem: @Singleton + @Inject constructor(context: Application) — the DI container injects the Application context automatically. For a Retrofit client, parameters (baseUrl, interceptors) are passed through a builder in the DI module.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also