You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中Sheet导致ObservableObject内存泄漏的解决方案问询

SwiftUI中Binding关联ViewModel的内存泄漏问题及解决方案

问题场景

在iOS 17+版本的SwiftUI里,用Binding关联ObservableObject类型的ViewModel时,只要通过.sheet()或.fullScreenCover()展示的视图间接引用了父视图的绑定属性,就会导致ViewModel实例无法被释放,出现内存泄漏。复现步骤如下:

  1. 定义ObservableObject模型:
final class Model: ObservableObject { 
   @Published var selectedValue: String?
}
  1. 父视图使用该模型:
struct ContentView: View { 
  @StateObject private var model = Model() 

  var body: some View { 
     SelectButton(selection: $model.selectedValue) 
  } 
}
  1. 带Sheet的子视图:
struct SelectButton: View { 
   @Binding var selection: String? 
    @State private var isPresented = false 
    let options: [String] = ["One", "Two"]

    var body: some View { 
       Button { isPresented = true } label: {
        Text(selection ?? "Select") 
       }
       .sheet(isPresented: $isPresented) { 
           VStack { 
               Text("List with options") 
               ForEach(options, id: \.self) { option in Text(option) }
            }
        }
    }
}

泄漏触发条件:弹出并关闭Sheet后返回上一页,Model实例无法被释放;不弹出Sheet或Sheet不引用父属性时则无泄漏。另外,Sheet内容定义为函数/计算属性、用EnvironmentObject传递模型时也会触发泄漏。

解决方案

1. 优化自定义弱绑定函数

你现有的weakBinding函数已经能规避泄漏,这里可以优化兼容性,去掉Value: ExpressibleByNilLiteral的限制,让它支持更多类型:

func weakBinding<O: ObservableObject, Value>(_ object: O, keyPath: ReferenceWritableKeyPath<O, Value>) -> Binding<Value> {
    Binding(
        get: { [weak object] in
            guard let object = object else {
                // 非可选类型返回默认值,可选类型返回nil
                return (Value.self as? ExpressibleByNilLiteral.Type)?.init(nilLiteral: ()) as? Value ?? {
                    fatalError("Weak binding object deallocated and Value has no default nil value")
                }()
            }
            return object[keyPath: keyPath]
        },
        set: { [weak object] newValue in
            object?[keyPath: keyPath] = newValue
        }
    )
}

调用方式不变:

SelectButton(selection: weakBinding(model, keyPath: \.selectedValue))

2. 将Sheet内容封装为独立视图并传递弱引用

把Sheet的内容抽成独立视图,通过初始化参数传递弱引用的模型,直接切断绑定的强引用链:

struct OptionSheetView: View {
    weak var model: Model?
    let options: [String]
    
    var body: some View {
        VStack {
            Text("List with options")
            ForEach(options, id: \.self) { option in
                Button(option) {
                    model?.selectedValue = option
                }
            }
        }
    }
}

然后调整SelectButton和父视图的调用逻辑:

struct ContentView: View {
    @StateObject private var model = Model()
    
    var body: some View {
        SelectButton(model: model, selection: $model.selectedValue)
    }
}

struct SelectButton: View {
    weak var model: Model?
    @Binding var selection: String?
    @State private var isPresented = false
    let options: [String] = ["One", "Two"]
    
    var body: some View {
        Button { isPresented = true } label: {
            Text(selection ?? "Select")
        }
        .sheet(isPresented: $isPresented) {
            OptionSheetView(model: model, options: options)
        }
    }
}

3. 临时替代方案:自定义弹出视图

在部分场景下,用自定义的弹出视图(比如通过ZStack+opacity控制显示)替代系统.sheet,也能绕过这个Bug,适合快速验证问题。

关于自定义弱绑定语法(如#viewModel.selectedValue)

Swift目前不支持自定义类似$的属性包装器投影语法——$是SwiftUI内置的语言层面特性,没法通过自定义扩展实现#这类投影符号。

不过可以给ObservableObject加扩展,实现更简洁的调用方式:

extension ObservableObject {
    func weakBinding<Value>(keyPath: ReferenceWritableKeyPath<Self, Value>) -> Binding<Value> {
        SwiftUI.Binding(
            get: { [weak self] in
                guard let self = self else {
                    return (Value.self as? ExpressibleByNilLiteral.Type)?.init(nilLiteral: ()) as? Value ?? {
                        fatalError("Weak binding object deallocated and Value has no default nil value")
                    }()
                }
                return self[keyPath: keyPath]
            },
            set: { [weak self] in self?[keyPath: keyPath] = $0 }
        )
    }
}

调用时可以简化成:

SelectButton(selection: model.weakBinding(keyPath: \.selectedValue))

虽然不如#model.selectedValue简洁,但已经比原函数调用紧凑很多。

内容的提问来源于stack exchange,提问作者Michał Ziobro

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 04:35:32