打造零泄漏的 Swift 代码:三套实用工具完整指南

原文: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 类型)
  • ✅ 支持 forEachcount 等便捷方法

工具 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 模式无开销。

组合使用:一条链路的最佳实践

  1. Coordinator 实现 MemoryLeakMonitorable,自动检测泄漏
  2. 内部持有子 Coordinator 用 AtomicWeakArray 线程安全存储
  3. 子 Coordinator 的 delegate 使用 @WeakArray 避免循环引用
  4. 共享缓存 用 AtomicDictionary 并发读写

总结 & 迁移建议

工具 适用场景 迁移成本
NSHashTable 简单无序集合 0 行代码
@WeakArray 需要顺序/重复 替换成属性包装器
AtomicWeakArray 多线程读写 ArrayAtomicWeakArray
AtomicDictionary 并发缓存、注册表 DictionaryAtomicDictionary
MemoryLeakMonitor Coordinator/VM/Service 加协议 + 1 行 validate(self)
相关推荐
zhyongrui1 天前
SnipTrip 发热优化实战:从 60Hz 到 30Hz 的性能之旅
ios·swiftui·swift
大熊猫侯佩2 天前
Neo-Cupertino 档案:撕开 Actor 的伪装,回归 Non-Sendable 的暴力美学
swift·observable·actor·concurrency·sendable·nonsendable·data race
2501_915921433 天前
在没有源码的前提下,怎么对 Swift 做混淆,IPA 混淆
android·开发语言·ios·小程序·uni-app·iphone·swift
00后程序员张4 天前
对比 Ipa Guard 与 Swift Shield 在 iOS 应用安全处理中的使用差异
android·开发语言·ios·小程序·uni-app·iphone·swift
大熊猫侯佩4 天前
星际穿越:SwiftUI 如何让 ForEach 遍历异构数据(Heterogeneous)集合
swiftui·swift·遍历·foreach·any·异构集合·heterogeneous
hjs_deeplearning4 天前
认知篇#15:ms-swift微调中gradient_accumulation_steps和warmup_ratio等参数的意义与设置
开发语言·人工智能·机器学习·swift·vlm
墨瑾轩5 天前
C# PictureBox:5个技巧,从“普通控件“到“图像大师“的蜕变!
开发语言·c#·swift
@大迁世界8 天前
Swift、Flutter 还是 React Native:2026 年你该学哪个
开发语言·flutter·react native·ios·swift
Swift社区9 天前
在Swift中实现允许重复的O(1)随机集合
开发语言·ios·swift
初级代码游戏10 天前
iOS开发 SwiftUI 8:NavigationView 导航
ios·swiftui·swift