SwiftUI中@Published属性为何残留内存痕迹?是否属于内存泄漏?(VIPER架构场景)
嘿,这个情况我在SwiftUI结合VIPER架构开发时也碰到过,咱们一步步拆解原因和解决办法:
核心原因
1. SwiftUI订阅机制的滞后清理
当你在View中通过@ObservedObject绑定Presenter时,SwiftUI会自动为@Published属性创建Combine订阅来监听状态变化。即使视图被关闭销毁,SwiftUI内部的订阅管理池可能不会立即清理这些订阅对应的键路径引用——尤其是当你直接访问presenter.isReminderActive这类属性时,SwiftUI会生成特定的键路径(比如\ExamplePresenter.isReminderActive)来监听,这些键路径的元数据可能会短暂残留在内存中,直到系统自动内存回收机制触发。
2. Combine Publisher的残留引用
@Published的底层依赖Combine框架的Publisher实现。如果Presenter在释放时,Combine的订阅没有被完全取消(比如SwiftUI的内部订阅没有及时收到完成信号),就会导致键路径相关的引用无法被立即回收。虽然Presenter实例本身已经被释放,但这些订阅关联的键路径信息可能会暂时留在内存中。
3. 视图层级的隐性缓存
有时候,UIHostingController被dismiss后,SwiftUI可能会暂时缓存视图相关的一些元数据(包括键路径),用于可能的快速重建。这种缓存通常是临时的,但在内存调试中会被检测到。
可行的解决方案
手动触发订阅清理:在Presenter的
deinit方法中,手动对所有@Published属性发送完成信号,帮助Combine清理订阅:extension ExamplePresenter { deinit { // 手动触发@Published属性的完成事件 _datasource.send(completion: .finished) // 如果是嵌套的state对象,同样处理 _states.send(completion: .finished) } }注:
_datasource是@Published自动生成的底层Publisher变量,命名规则为下划线加属性名。用弱引用包装Presenter:在View中避免直接持有Presenter的强引用,改用弱引用包装器,让SwiftUI更容易清理订阅:
struct WeakPresenter<T: ObservableObject> { weak var value: T? init(_ value: T) { self.value = value } } struct ExampleViewView<T: ObservablePresenter>: View where T.ViewStates == ExampleStates { @ObservedObject var weakPresenter: WeakPresenter<T> var body: some View { guard let presenter = weakPresenter.value else { return Color.clear // 处理Presenter已释放的边界情况 } if !presenter.isReminderActive { Color.red } } }初始化View时传入
WeakPresenter(presenter)即可。确保HostingController被完全释放:在presenting VC中,dismiss视图后手动置空
UIHostingController的引用,避免隐性强引用:var viewController: ExampleUIHostingController? // 弹出视图 func presentExampleView() { let presenter = ExamplePresenter() viewController = ExampleUIHostingController(rootView: ExampleSwiftUIView(presenter: presenter)) present(viewController!, animated: true) } // 关闭视图后 func dismissExampleView() { dismiss(animated: true) { self.viewController = nil // 手动置空,释放引用链 } }
额外说明
这种键路径残留通常是暂时的,系统ARC会在后续内存回收周期中清理掉这些痕迹。如果内存调试显示的是长期残留,那大概率是订阅未被正确取消,或者存在隐性强引用链——可以用Xcode的内存图工具,查看键路径引用的持有者,进一步定位问题。
内容的提问来源于stack exchange,提问作者HVR

