在 Swift 的世界里,Singleton(单例模式) 是我们最熟悉的老朋友,而 Global Actor(全局 Actor) 则是 Swift 5.5 引入并发模型后的新伙伴。两者都能帮我们管理全局共享资源,但谁才是现代 Swift 项目的最佳选择?本文将带你深入理解两者的核心差异、适用场景,并结合代码示例给出实战建议。
为什么需要 Singleton?
Singleton 的核心价值
- 唯一实例:确保类只有一个实例(如网络管理器、数据库连接)。
- 全局访问:通过
shared
属性在任何地方访问。
传统 Singleton 的实现
swift
// 传统单例:非线程安全
class NetworkManager {
static let shared = NetworkManager()
private init() {} // 防止外部实例化
func fetchData() {
// 可能因多线程访问导致数据竞争
}
}
线程安全的 Singleton
swift
// 线程安全版本(手动同步)
class NetworkManager {
static let shared = NetworkManager()
private let queue = DispatchQueue(label: "com.app.network", qos: .utility)
private init() {}
func fetchData(completion: @escaping (Data) -> Void) {
queue.async {
// 模拟网络请求
completion(Data())
}
}
}
痛点:需要手动管理锁或队列,代码冗长且易出错。
什么是 Global Actor?
Global Actor 的核心价值
- 自动线程安全:通过
@globalActor
标记,Swift 自动序列化代码执行。 - 无缝集成 async/await:与 Swift 并发模型完美契合。
定义 Global Actor
swift
// 1. 定义全局 Actor
@globalActor
actor NetworkActor {
static let shared = NetworkActor()
}
// 2. 使用 Global Actor 的单例
@NetworkActor
class NetworkManager {
static let shared = NetworkManager()
private init() {}
func fetchData() async -> Data {
// 自动线程安全,无需手动锁
return Data()
}
}
调用 Global Actor 单例
swift
Task {
let data = await NetworkManager.shared.fetchData()
print("数据大小: \(data.count)")
}
优势:无需 DispatchQueue
,代码更简洁,且天然支持 async/await
。
深度对比:Singleton vs Global Actor
维度 | 传统 Singleton | Global Actor Singleton |
---|---|---|
线程安全 | 需手动实现(锁/DispatchQueue) | 自动序列化访问 |
代码复杂度 | 高(需处理同步逻辑) | 低(标记 @ActorName 即可) |
async/await 支持 | 不直接支持(需回调或手动转换) | 完全支持(原生 async/await) |
灵活性 | 可精细控制并发粒度(如部分方法异步) | 整个类强制序列化(可能过度保护) |
性能 | 无额外开销(但锁竞争可能影响性能) | 轻微调度开销(由 Swift 运行时优化) |
适用场景 | 简单共享资源、遗留代码 | 高并发读写、现代 Swift 并发模型 |
何时选择传统 Singleton?
适用场景
- 资源只读:如配置管理器,无并发修改风险。
- 遗留项目:未迁移到 Swift 并发模型。
- 精细控制:需自定义并发策略(如读写锁)。
示例:只读配置管理器
swift
class AppConfig {
static let shared = AppConfig()
let apiBaseURL = "https://api.example.com"
let timeout: TimeInterval = 30
private init() {} // 只读无需线程安全
}
何时选择 Global Actor?
适用场景
- 共享可变状态:如缓存、数据库写入。
- 现代并发模型:已广泛使用
async/await
。 - 减少心智负担:避免手动同步。
示例:线程安全的数据库
swift
@globalActor
actor DatabaseActor {
static let shared = DatabaseActor()
}
@DatabaseActor
class DatabaseManager {
static let shared = DatabaseManager()
private var cache: [String: Data] = [:]
func save(key: String, value: Data) async {
cache[key] = value
}
func fetch(key: String) async -> Data? {
return cache[key]
}
}
混合模式:全局访问 + 线程安全
场景:需要全局访问且高并发
swift
// 定义 Actor
@globalActor
actor AnalyticsActor {
static let shared = AnalyticsActor()
}
// 混合单例
@AnalyticsActor
class AnalyticsManager {
static let shared = AnalyticsManager()
private var events: [String] = []
func track(event: String) async {
events.append(event)
}
func getEvents() async -> [String] {
return events
}
}
调用示例
swift
Task {
await AnalyticsManager.shared.track(event: "user_login")
let events = await AnalyticsManager.shared.getEvents()
}
性能对比与最佳实践
性能测试(简化版)
swift
// 传统单例(DispatchQueue)
class TraditionalManager {
static let shared = TraditionalManager()
private let queue = DispatchQueue(label: "test")
var counter = 0
func increment() {
queue.async {
self.counter += 1
}
}
}
// Global Actor 单例
@globalActor
actor PerformanceActor {
static let shared = PerformanceActor()
}
@PerformanceActor
class ActorManager {
static let shared = ActorManager()
var counter = 0
func increment() async {
counter += 1
}
}
结果:Global Actor 在 高并发写入 时性能更稳定(无锁竞争)。
最佳实践总结
- 新项目:优先 Global Actor(除非性能极端敏感)。
- 旧项目:逐步迁移,可保留传统 Singleton。
- 混合场景:Actor 管理可变状态,传统单例管理只读数据。
结论:现代 Swift 的正确姿势
选择 | 结论 |
---|---|
传统 Singleton | 仅适用于只读资源或遗留代码。 |
Global Actor | 现代 Swift 的首选,线程安全且代码简洁。 |
一句话总结:
除非有明确理由(如遗留代码或极端性能需求),Global Actor 是管理共享可变资源的现代解决方案。它让开发者专注于业务逻辑,而非线程安全细节。
附录:迁移指南(传统 Singleton → Global Actor)
步骤 1:标记类为 Actor
swift
// 旧代码
class OldManager {
static let shared = OldManager()
private init() {}
}
// 新代码
@globalActor
actor NewManager {
static let shared = NewManager()
private init() {}
}
步骤 2:调整调用方式
swift
// 旧调用
OldManager.shared.doSomething()
// 新调用(需 await)
await NewManager.shared.doSomething()
步骤 3:处理回调转 async
swift
// 旧回调方式
OldManager.shared.fetchData { result in
// 处理结果
}
// 新 async 方式
let result = await NewManager.shared.fetchData()