SwiftUI中绑定Sheet的Item未销毁引发循环引用问题
问题分析
循环引用的核心原因是闭包的强引用链:
- 创建
Item实例时,传入的闭包{ item = nil }会捕获ContentView的实例(因为item是ContentView的@State属性)。 Item实例持有该闭包,而ContentView又通过@State var item持有Item实例,形成ContentView → Item → 闭包 → ContentView的循环引用,导致Item的deinit永远无法触发。
解决方案
方案1:给闭包添加弱引用捕获
修改ContentView中创建Item的代码,通过[weak self]让闭包弱引用ContentView,打破强引用链:
Button("Show") { item = Item { [weak self] in self?.item = nil } }
这样当item被设为nil时,Item实例不再被强引用,就能正常触发deinit。
方案2:利用SwiftUI环境dismiss,彻底消除闭包传递
遵循SwiftUI的设计理念,不用手动传递dismiss闭包,直接在子视图中使用@Environment(\.dismiss)来关闭Sheet:
class Item: Identifiable { init() { print("init >> item \(Unmanaged.passUnretained(self).toOpaque())") } deinit { print("deinit >> item \(Unmanaged.passUnretained(self).toOpaque())") } func start() -> some View { ViewTwo() } } struct ContentView: View { @State private var item: Item? { didSet { print(item) } } var body: some View { Button("Show") { item = Item() } .sheet(item: $item) { item in item.start() } } } struct ViewTwo: View { @Environment(\.dismiss) private var dismiss var body: some View { VStack { Button("Dismiss") { dismiss() } } } }
这种方式从根源上避免了闭包引用带来的循环问题,代码更简洁也更符合SwiftUI的范式。
方案3:将Item的dismiss改为可选类型(保留闭包传递场景)
如果业务必须保留闭包传递,可以将Item中的dismiss设为可选类型,并在deinit中置空,配合弱引用捕获使用:
class Item: Identifiable { var dismiss: (() -> Void)? init(dismiss: @escaping () -> Void) { self.dismiss = dismiss print("init >> item \(Unmanaged.passUnretained(self).toOpaque())") } deinit { print("deinit >> item \(Unmanaged.passUnretained(self).toOpaque())") dismiss = nil } func start() -> some View { ViewTwo(dismiss: dismiss!) } } // 创建Item时仍需添加弱引用捕获 Button("Show") { item = Item { [weak self] in self?.item = nil } }
内容的提问来源于stack exchange,提问作者Boyan Pavlov
相关产品推荐
相关产品推荐

