swift 在@State中保留多个闭包以保持对@StateObject的引用的内存泄漏

y1aodyip  于 12个月前  发布在  Swift
关注(0)|答案(1)|浏览(94)

我在代码中遇到了非常奇怪的内存泄漏。它主要是由使用@escaping闭包和嵌套这个闭包引起的。我创建了一个最小的可重复的例子来演示这种内存泄漏。

import SwiftUI

struct IdentifiableAction: Identifiable, Hashable {
    
    public let id: UUID
    public let action: () -> Void
    
    public init(action: @escaping () -> Void, id: UUID = .init()) {
        self.id = id
        self.action = action
    }
    
    public func callAsFunction() {
        action()
    }
    
    public static func == (lhs: IdentifiableAction, rhs: IdentifiableAction) -> Bool {
        lhs.id == rhs.id
    }
    
    public func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}

extension View {
    func check(action: Binding<IdentifiableAction?>, label: String) -> some View {
        self
            .onChange(of: action.wrappedValue) { _ in
                guard action.wrappedValue != nil else { return }
                print("[DEBUG] Check action \(label)")
                action.wrappedValue?()
            }
    }
}

struct ContentView: View {
    
    var body: some View {
        TabView {
            NavigationView {
                VStack {
                    NavigationLink("DetailView") {
                        DetailView()
                    }
                }
                .padding()
            }
            .tabItem { Text("Tab 1") }
            
            
            Text("Page 2")
                .tabItem { Text("Tab 2") }
            Text("Page 3")
                .tabItem { Text("Tab 3") }
        }
    }
}

class DetailViewModel: ObservableObject {
    
    init() { 
        print("[DEBUG]: init DetailViewModel")
    }
    
    deinit {
        print("[DEBUG]: deinit DetailViewModel")
    }
    
    func action() {
        print("[DEBUG]: view model action")
    }
}

struct DetailView: View {
    
    @State private var actionCheck1: IdentifiableAction?
    @State private var actionCheck2: IdentifiableAction?
    @State private var actionCheck3: IdentifiableAction?
    
    @StateObject private var viewModel = DetailViewModel()
    
    var body: some View {
        VStack {
            
            Text("Detail view!")
            
            Button("Execute checked action") {
                actionCheck1 = IdentifiableAction {
                    actionCheck2 = IdentifiableAction {
                        actionCheck3 = IdentifiableAction {
                            viewModel.action()
                        }
                    }
                }
            }
        }
        .padding()
        .check(action: $actionCheck1, label: "1")
        .check(action: $actionCheck2, label: "2")
        .check(action: $actionCheck3, label: "3")
    }
}

字符串
一般来说,我希望在执行一些“检查”后执行操作。这里的检查是简单的打印语句,通过在应用程序中,它可以显示一些视图,对话框,警报与不同的条件。但为了保持示例简单和最小化,我只是打印有关检查完成的语句,然后在视图模型中执行操作。如下所示:

[DEBUG] Check action 1
[DEBUG] Check action 2
[DEBUG] Check action 3
[DEBUG]: view model action


我希望每个检查都像视图修改器或视图扩展上的函数(有点类似于sheet(item: $presentedItem)。所以我有视图扩展函数.check(action: $actionCheck1)为了能够使用action作为这样的触发器,我需要将每个action Package 在IdentifiableAction结构中,该结构实现了Identifiable协议和Hashable协议,每次创建action时,我都会为每个action添加唯一的UUID。
然后我这样使用这个视图修改器:

.check(action: $actionCheck1, label: "1")
.check(action: $actionCheck2, label: "2")
.check(action: $actionCheck3, label: "3")


好了,现在执行的动作是由多个检查被拦截的动作(IdentifiableAction),必须执行和传递执行适当的动作viewModel。我做的动作:

Button("Execute checked action") {
                actionCheck1 = IdentifiableAction {
                    actionCheck2 = IdentifiableAction {
                        actionCheck3 = IdentifiableAction {
                            viewModel.action()
                        }
                    }
                }
            }


上面的代码会导致内存泄漏。理论上我们在结构体的View中,我们使用结构体的IdentifiableAction,唯一的引用类型是DetailsViewModel类和传递给IdentifiableAction的闭包。
所以内存泄漏的问题是在IdentifiableActions中使用的嵌套闭包。
我已经厌倦了使用像[weak viewModel]这样的捕获列表,但它只适用于单个IdentifiableAction。

Button("Execute checked action") {
    actionCheck1 = IdentifiableAction { [weak viewModel] in
         viewModel?.action()
    }
}


所以上面的代码防止了内存泄漏。
但是在嵌套闭包中添加额外的[weakviewModel]捕获列表并没有改变任何东西,内存泄漏仍然存在。

Button("Execute checked action") {
       actionCheck1 = IdentifiableAction { [weak viewModel] in
           actionCheck2 = IdentifiableAction { [weak viewModel] in
              actionCheck3 = IdentifiableAction { [weak viewModel] in
                  viewModel?.action()
             }
          }
      }
}

ct3nt3jp

ct3nt3jp1#

好了,我已经解决了这个问题。问题是我在视图修饰符中执行action之后没有将其设置为nil。所以这个action总是保存在@State属性 Package 器中,并且也保存了它们自己。在重置than为nil之后,所有内容都正确地与视图模型本身一起释放。我也不需要在捕获列表中使用任何weak。只是按照预期工作。

相关问题