题目
设计线程安全的懒加载属性包装器,支持值类型和引用类型并避免循环依赖
信息
- 类型:问答
- 难度:⭐⭐⭐
考点
属性包装器, 线程安全, 懒加载, 值类型与引用类型, 循环依赖
快速回答
实现线程安全的懒加载属性包装器需要解决以下核心问题:
- 使用锁(如
os_unfair_lock)确保多线程环境下的初始化安全 - 通过内部引用容器支持值类型和引用类型
- 使用
@autoclosure延迟初始化执行 - 在闭包中强制使用
[weak self]避免循环依赖 - 初始化后释放闭包减少内存占用
原理说明
线程安全懒加载属性包装器需要解决三个核心问题:1) 多线程环境下初始化操作的原子性;2) 值类型(struct)和引用类型(class)的兼容性;3) 初始化闭包中的循环依赖风险。通过结合锁机制、内部引用容器和弱引用捕获,可实现安全高效的懒加载。
代码实现
import Foundation
import os.lock
@propertyWrapper
struct ThreadSafeLazy<T> {
private final class Storage {
var value: T?
}
private let storage = Storage()
private var lock = os_unfair_lock()
private var initializer: (() -> T)?
init(wrappedValue initializer: @escaping @autoclosure () -> T) {
self.initializer = initializer
}
var wrappedValue: T {
mutating get {
os_unfair_lock_lock(&lock)
defer { os_unfair_lock_unlock(&lock) }
if let value = storage.value {
return value
}
guard let initializer = initializer else {
fatalError("重复访问已释放的懒加载属性")
}
let value = initializer()
storage.value = value
self.initializer = nil // 释放闭包
return value
}
set {
os_unfair_lock_lock(&lock)
defer { os_unfair_lock_unlock(&lock) }
storage.value = newValue
}
}
}
// 使用示例
class DataProcessor {
@ThreadSafeLazy var heavyResource: HeavyResource = {
// 强制使用weak避免循环依赖
[weak self] in
guard let self = self else { return HeavyResource() }
return HeavyResource(config: self.config)
}()
var config: Configuration
init(config: Configuration) {
self.config = config
}
}
struct Point {
@ThreadSafeLazy var distanceCache: Double = {
sqrt(x*x + y*y) // 值类型安全使用
}()
var x, y: Double
}最佳实践
- 锁的选择:优先使用
os_unfair_lock(性能优于NSLock) - 内存管理:初始化后立即释放闭包,减少内存占用
- 循环依赖防护:在闭包参数声明处强制使用
[weak self] - 值类型支持:通过内部
Storage类绕过值类型的复制语义 - 错误处理:添加
defer确保锁始终释放,避免死锁
常见错误
- 线程竞争:未加锁导致多次初始化(使用
os_unfair_lock_lock解决) - 循环引用:闭包内捕获强引用
self(强制[weak self]) - 值类型失效:直接存储值导致结构体复制时值丢失(通过引用容器解决)
- 闭包滞留:初始化后未释放闭包(显式置
nil)
扩展知识
- 锁的性能对比:
os_unfair_lock>pthread_mutex>NSLock>@synchronized - Swift 5.5+ 替代方案:使用
actor隔离访问(但属性包装器不能直接声明为actor) - 内存布局影响:值类型中使用引用容器会增加一次指针跳转
- 与标准lazy对比:标准
lazy var既不线程安全也不支持弱引用捕获