原文:Three Practical Tools for Managing References and Detecting Memory Leaks in Swift
为什么需要这三件套?
痛点 | 工具 | 一句话作用 |
---|---|---|
保留循环导致 Coordinator/VM 不释放 | MemoryLeakMonitor | 运行时断言实例数量 |
Delegate 数组、观察者列表强引用 | @WeakArray / AtomicWeakArray | 自动剔除已释放对象 |
多线程读写同一字典 | AtomicDictionary | 无锁并发安全读写 |
工具 1:弱引用集合
系统自带方案:NSHashTable
swift
private final class ReferenceRepository {
// NSHashTable 只存弱引用,对象释放后自动移除
private var references = NSHashTable<AnyObject>.weakObjects()
func add(_ ref: AnyObject) -> Int {
references.add(ref)
return references.count // 返回当前存活数量
}
}
- ✅ 线程安全(内部使用锁)
- ❌ 无序、不可重复、只能存
AnyObject
自定义 WeakArray
------ 有序、可重复、支持泛型
轻量级包装器 WeakObject
swift
/// 弱引用包装器,内部使用闭包捕获弱引用
final class WeakObject<T> {
private let handler: () -> T?
// 通过计算属性实时获取弱引用对象
var value: T? { handler() }
init(_ value: T) {
let object = value as AnyObject
// 巧用闭包捕获原始对象的弱引用
handler = { [weak object] in object as? T }
}
}
@WeakArray
属性包装器
swift
@propertyWrapper
struct WeakArray<Element> {
private var storage = [WeakObject<Element>]()
// 初始值赋值需要用到的构造器,第一个参数标签必须是wrappedValue
init(wrappedValue: [Element]) {
self.wrappedValue = wrappedValue
}
/// 读取时自动剔除 nil
var wrappedValue: [Element] {
get { storage.compactMap { $0.value } }
set { storage = newValue.map(WeakObject.init) }
}
}
使用示例:多播委托
swift
protocol MyDelegate: AnyObject {
func didUpdate()
}
final class EventBroadcaster {
// 这里会调用WeakArray的init(wrappedValue:)构造器
@WeakArray private var subscribers: [MyDelegate] = []
// 添加订阅
func subscribe(_ subscriber: MyDelegate) {
subscribers.append(subscriber)
}
func notifyAll() {
subscribers.forEach { $0.didUpdate() }
}
}
工具 2:线程安全的弱引用数组 AtomicWeakArray
@WeakArray
本身不是线程安全,多线程同时读写会崩溃。
用 DispatchQueue
实现 并发读、排他写 的版本:
swift
final class AtomicWeakArray<Element> {
// 并发队列
private let queue = DispatchQueue(
label: "atomic-weak-array",
attributes: .concurrent
)
private var storage: [WeakObject<Element>] = []
/// 只读快照,内部同步
var all: [Element] {
queue.sync { storage.compactMap { $0.value } }
}
func append(_ element: Element) {
queue.async(flags: .barrier) {
self.storage.append(WeakObject(element))
}
}
func removeAll() {
queue.async(flags: .barrier) { self.storage.removeAll() }
}
func forEach(_ body: (Element) throws -> Void) rethrows {
try queue.sync {
try storage.compactMap { $0.value }.forEach(body)
}
}
var count: Int { queue.sync { storage.compactMap { $0.value }.count } }
}
- ✅ 读写并行,写操作互斥
- ✅ 保持引用语义(class 类型)
- ✅ 支持
forEach
、count
等便捷方法
工具 3:线程安全的字典 AtomicDictionary
标准 Dictionary
是值类型,多线程读写会触发 数据竞争 或 CoW 复制。
封装一个基于 DispatchQueue
的并发字典:
swift
class AtomicDictionary<Key: Hashable, Value> {
private let queue = DispatchQueue(label: "atomic-dict", attributes: .concurrent)
private var storage: [Key: Value] = [:]
subscript(key: Key) -> Value? {
get { queue.sync { storage[key] } }
set { queue.async(flags: .barrier) { self.storage[key] = newValue } }
}
/// 原子"读取或插入"
subscript(key: Key, default value: @autoclosure () -> Value) -> Value {
get {
if let v = self[key] { return v }
let new = value()
self[key] = new
return new
}
}
}
工具 4:内存泄漏监测器 MemoryLeakMonitor
协议:声明最大实例数
swift
protocol MemoryLeakMonitorable: AnyObject {
/// 允许同时存活的最大实例数量
var max: Int { get }
}
extension MemoryLeakMonitorable {
/// 默认用类名作为标识
var description: String { String(describing: self) }
}
单例监测器
swift
final class MemoryLeakMonitor {
private static let shared = MemoryLeakMonitor()
private let dict: AtomicDictionary<String, ReferenceRepository> = .init()
private init() {}
/// 在 DEBUG 环境下调用
static func validate(_ instance: MemoryLeakMonitorable) {
let count = shared.dict[instance.description, default: .init()]
.count(with: instance)
assert(
count <= instance.max,
"内存泄漏!\(instance.description) 当前实例数:\(count)"
)
}
}
/// 弱引用仓库,内部使用线程安全的 AtomicWeakArray
private final class ReferenceRepository {
private var refs: AtomicWeakArray<AnyObject> = .init()
func count(with ref: AnyObject) -> Int {
refs.append(ref)
return refs.count
}
}
使用示例:Coordinator 自检
swift
class Coordinator: MemoryLeakMonitorable {
// 只能有一个实例
var max: Int { 1 }
init() {
#if DEBUG
MemoryLeakMonitor.validate(self)
#endif
}
}
final class ArchiveCoordinator: Coordinator {
override var max: Int { 2 } // 允许两个并存
}
每次 init
时自动校验,Debug 模式下一旦发现超限立即断言中断,Release 模式无开销。
组合使用:一条链路的最佳实践
- Coordinator 实现
MemoryLeakMonitorable
,自动检测泄漏 - 内部持有子 Coordinator 用
AtomicWeakArray
线程安全存储 - 子 Coordinator 的 delegate 使用
@WeakArray
避免循环引用 - 共享缓存 用
AtomicDictionary
并发读写
总结 & 迁移建议
工具 | 适用场景 | 迁移成本 |
---|---|---|
NSHashTable |
简单无序集合 | 0 行代码 |
@WeakArray |
需要顺序/重复 | 替换成属性包装器 |
AtomicWeakArray |
多线程读写 | 把 Array → AtomicWeakArray |
AtomicDictionary |
并发缓存、注册表 | 把 Dictionary → AtomicDictionary |
MemoryLeakMonitor |
Coordinator/VM/Service | 加协议 + 1 行 validate(self) |