Singleton — what is it, a single class instance in iOS and Android

Author: IT Sectr Published: 2026-02-17 Reading time: 8 min

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 — guarantees one class instance per application
  • Global access point — static property shared or companion object
  • Thread safety — synchronization required for correct operation in multithreaded environments
  • Criticism — Singleton complicates testing and creates hidden dependencies
  • Alternatives — Dependency Injection, Service Locator to replace Singleton

What is Singleton: the essence of the singleton pattern?

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.

Singleton in iOS with Swift: shared and static properties

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.

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
    }
}

// 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.

Singleton in Android with Kotlin: companion object and object

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().

kotlin
// 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: dispatch_once, synchronized and lock

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.

LanguageMechanismThread safetyLazy initialization
Swiftstatic letdispatch_once (automatic)Yes, on first access
Kotlin objectObject declarationClass initializer is thread-safeYes, on first access
Kotlin companionsynchronized + @VolatileDouble-checked lockingYes, via lazy or synchronized
Javasynchronized + volatileDouble-checked lockingYes, 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.

Singleton vs Dependency Injection: when to use

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

Is Singleton an anti-pattern?

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.

How to test code that uses Singleton?

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.

How is Kotlin object different from Java Singleton?

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.

Can Singleton be inherited?

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.

How to pass parameters to a Singleton in Android?

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

  • Singleton — a pattern with a single instance and global access
  • Swift shared — static let with compiler-guaranteed thread safety
  • Kotlin object — lazy initialization without boilerplate code
  • Thread safety — double-checked locking for Java, automatic for Swift/Kotlin
  • Apple SDK — UIApplication.shared, UserDefaults.standard, FileManager.default
  • Android SDK — Retrofit, Room, SharedPreferences via Singleton managers
  • Alternatives — Dependency Injection for testable code

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.

Discuss the project

Read also