Singleton — 什么是单例,iOS 和 Android 中的类单实例

作者: IT Sectr 发布日期: 2026-02-17 阅读时间: 8 分钟

Singleton(单例)—— 一种创建型模式,确保类只有一个实例并提供全局访问点。Singleton 广泛应用于移动开发中的共享资源:网络客户端、数据库、设置管理器。该模式在经典著作 GoF(1994)中有所描述,至今仍是最广为人知的模式之一。更多详情请参阅 Refactoring Guru:Singleton

要点

  • Singleton — 确保整个应用程序中类只有一个实例
  • 全局访问点 — 静态属性 shared 或 companion object
  • Thread safety — 需要在多线程环境中进行同步以确保正确运行
  • 批评 — Singleton 使测试复杂化并创建隐藏依赖
  • 替代方案 — 依赖注入、服务定位器替代 Singleton

什么是 Singleton:单例模式的本质?

Singleton(单例)—— 由 GoF(Gang of Four)于 1994 年描述的创建型设计模式。该模式解决两个问题:将类实例的创建限制为一个对象,并提供对该对象的全局访问。Singleton 对于必须唯一的资源非常有用:会话工厂、图像缓存、数据库连接管理器、Crashlytics 或 Analytics 客户端。

Singleton 的实现 需要私有构造函数(禁止外部创建)、包含唯一实例的静态字段和静态访问方法(shared、instance、getInstance)。客户端调用 Singleton.shared.method(),无需关心对象的创建。该模式在 iOS 和 Android 中很流行:URLSession.shared、UserDefaults.standard、FirebaseApp.sharedInstance — 这些都是 Singleton。然而,过度使用 Singleton 会导致 Global State 反模式。

Singleton 的问题 — 隐藏依赖(类隐式依赖于 Singleton 对象)、测试困难(在测试中无法在不额外努力的情况下替换实例)、违反单一职责原则(Singleton 同时管理自身实例和业务逻辑)。现代移动开发更倾向于使用 DI(Dagger、Hilt、Swinject)来管理唯一实例 — DI 容器创建一次对象并通过构造函数注入。

iOS 中 Swift 的 Singleton:shared 和静态属性

Swift Singleton 通过具有私有初始化器的静态属性 shared 实现。从 Swift 3 开始,静态属性的延迟初始化保证是线程安全的 — 编译器通过 dispatch_once 自动添加同步。只需声明 static let shared = Class() 并将 init() 设为私有即可。Swift 在初始化后不需要额外的同步来支持单线程访问。

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

// 使用
let data = try await NetworkManager.shared.fetchData(from: url)

Apple Singleton — 在 iOS SDK 中,许多对象使用 Singleton:UIApplication.shared、UIScreen.main、FileManager.default、NotificationCenter.default、UserDefaults.standard。Apple 将 Singleton 用于物理上唯一的服务(一个屏幕、一个应用程序)。开发人员将此模式复制到自己的服务中。在 SwiftUI 中,对 Singleton 的全局访问被 Environment 和 @EnvironmentObject 取代,从而提高了可测试性。

Android 中 Kotlin 的 Singleton:companion object 和 object

Kotlin Singleton — 最简单的方式:关键字 object 声明一个单例类,在首次访问时进行延迟初始化。Kotlin object 是线程安全的,不需要额外的同步。如果需要带构造函数参数的 Singleton,则使用带有 lazy 委托的 companion object。在 Android 中,Singleton 通常用于 Application 上下文和通过 Application.onCreate() 初始化的服务。

kotlin
// 选项 1:object — 不带参数的简单 Singleton
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) }
}

// 选项 2:companion object — 带参数的 Singleton
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 — 许多 Android 系统服务实现了 Singleton:context.getSystemService()、Room.databaseBuilder()、Retrofit.Builder()。示例包括 SharedPreferences、MediaPlayer、AudioManager。在 Android 应用程序中,Singleton 通常用于存储库、管理器和工厂。Google 建议用 DI(Hilt、Koin)替代 Singleton,其中 Singleton 作用域(Scope.Singleton 或 @Singleton)由容器管理,类保持可测试性。

线程安全:dispatch_once、synchronized 和锁

线程安全 — 在多线程环境中对 Singleton 的关键要求。没有同步,两个线程可能同时检查 instance == null 并创建两个实例。解决方案 — 在首次创建时加锁,初始化后解锁。在 Swift 中,静态属性(static let)默认是线程安全的。在 Kotlin 中,object 是线程安全的。对于 Kotlin 中的 Java 风格,使用 synchronized 或 @Volatile + double-check locking。

语言机制线程安全延迟初始化
Swiftstatic letdispatch_once(自动)是,首次访问时
Kotlin objectObject declaration类初始化器线程安全是,首次访问时
Kotlin companionsynchronized + @Volatile双重检查锁定是,通过 lazy 或 synchronized
Javasynchronized + volatile双重检查锁定是,在 getInstance() 中

双重检查锁定 — 用于延迟初始化 Singleton 的模式。第一次检查不带同步(如果实例已存在则快速),第二次 — 在 synchronized 内部(仅由一个线程创建)。@Volatile 保证所有线程对更改的可见性。没有 volatile,其他线程可能会看到部分创建的对象。在 Kotlin 中,使用 LazyThreadSafetyMode.SYNCHRONIZED 的 lazy 委托自动实现双重检查锁定。

Singleton 与依赖注入:何时使用

依赖注入 — 管理唯一实例的 Singleton 替代方案。DI 容器(Dagger、Hilt、Koin、Swinject)在 Singleton 作用域中创建一次对象并通过构造函数注入。类不知道自己的 Singleton 状态 — 这由容器决定。代码变得可测试:在测试中,DI 模块被 mock 模块替换。DI 的优点:构造函数中的显式依赖、可覆写性、统一的生命周期。

何时 Singleton 是合理的 — 系统级对象:Crashlytics、Analytics、Logging。这些服务在 AppDelegate/Application 中初始化一次,并在各处使用。DI 对它们来说过于复杂。Singleton 也适用于图像缓存(NSCache、Coil、Glide),其中全局访问因性能而合理。对于其他所有情况,DI 更可取:它使依赖关系可见,简化了测试和重构。

混合方法 — 具有测试覆写能力的 Singleton。在 Swift 中,使用协议 + 静态属性,测试可以替换该属性(例如,通过 URLProtocol 替换 URLSession)。在 Kotlin 中 — 具有可注入属性的开放类,测试通过反射或 setter 设置 mock。这种方法保持了 Singleton 的简单性,同时提供了测试工具。Google 为 Android 推荐 Hilt,Apple 不为 iOS 强制使用 DI — 选择取决于团队。

常见问题

Singleton 是反模式吗?

不,Singleton 是 GoF 模式,但其经常性的错误使用使其变成了 Global State 反模式。Singleton 对于物理上唯一的资源(屏幕、打印机、文件系统)是合理的。当 Singleton 用于管理数据时会出现问题:隐藏依赖、测试困难、违反单一职责原则。现代替代方案 — 具有 Singleton 作用域的 DI。

如何测试使用 Singleton 的代码?

三种方法:(1)通过协议 — Singleton 实现协议,测试替换实现;(2)通过 DI — Singleton 作为依赖通过构造函数注入;(3)通过重置方法 — Singleton 具有在测试中重置状态的方法(仅用于测试构建)。第一种方法更可取,第三种 — 对生产环境危险。Swift 允许在测试中通过运行时操作替换 shared 属性。

Kotlin object 与 Java Singleton 有何不同?

Kotlin object — 一种语言构造,在字节码级别创建 Singleton。与具有私有构造函数和 getInstance() 的 Java 实现不同,object 保证线程安全、延迟初始化和禁止继承。Java Singleton 需要手动同步(synchronized)和 volatile 才能在多线程环境中正确运行。Kotlin object — Android 中最安全、最简洁的方式。

Singleton 可以继承吗?

继承 Singleton 会破坏该模式:如果 Singleton 类可以被继承,子类可以创建第二个实例,破坏唯一性。在 Swift 中,final class 禁止继承。Kotlin object 不能被继承(object 是密封的)。如果需要具有变体的 Singleton,请使用具有 Singleton 作用域的 DI 容器:它保证一个实例并通过接口支持继承。

如何在 Android 中向 Singleton 传递参数?

参数通过 init(context: Application) 或 getInstance(param) 传递。Kotlin object 不接受参数 — 使用带有工厂方法 getInstance(param) 的 companion object。Hilt 解决了这个问题:@Singleton + @Inject constructor(context: Application) — DI 容器自动注入 Application 上下文。对于 retrofit 客户端,参数(baseUrl、interceptors)通过 DI 模块中的 builder 传递。

总结

  • Singleton — 具有唯一实例和全局访问的模式
  • Swift shared — 具有来自编译器的线程安全的 static let
  • Kotlin object — 无需额外代码的延迟初始化
  • Thread safety — Java 使用双重检查锁定,Swift/Kotlin 自动处理
  • Apple SDK — UIApplication.shared、UserDefaults.standard、FileManager.default
  • Android SDK — Retrofit、Room、SharedPreferences 通过 Singleton 管理器
  • 替代方案 — 依赖注入以实现可测试代码

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读