【问题标题】:Memory leak when initializing @ObservedObject inside View在 View 中初始化 @ObservedObject 时发生内存泄漏
【发布时间】:2019-11-17 05:12:48
【问题描述】:

我有一个ViewModel 类,它是一个ObservableObject,我在初始化其相应视图时对其进行了初始化。似乎如果我对视图内的ViewModel 有任何绑定,ViewModel 就会泄露。

例如,如果我在工作表中显示所述视图,则每次显示工作表时,都会分配一个新引用,并且在关闭工作表时不会释放它。引用计数随着我展示工作表的次数不断增加。

我是否遗漏了什么,或者@ObservedObject 属性包装器不应该以这种方式使用?

这是一个展示问题的简单示例。永远不会为 ViewModel 调用 deinit 函数

struct NewContactView: View {

    class ViewModel: ObservableObject {

        @Published var firstName = ""
        @Published var lastName = ""
        @Published var email = ""
        @Published var phoneNumber = ""

        init() {
            print("INIT")
        }

        deinit {
            print("DEINIT")
        }

    }

    @ObservedObject private var viewModel = ViewModel()

    var didCreateNewContact: (Contact) -> Void = { _ in }

    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("Names")) {
                    TextField("First Name", text: $viewModel.firstName)
                    TextField("Last Name", text: $viewModel.lastName)
                }

                Section(header: Text("Details")) {
                    TextField("Email", text: $viewModel.email)
                    TextField("Phone Number", text: $viewModel.phoneNumber)
                }

                Button(action: {}) {
                    Text("Save")
                }
            }
        }
    }
}

编辑:添加了显示工作表的代码

struct ContactsListView: View {

    @EnvironmentObject var contactStore: ContactStore

    @State private var isCreatingNewContact = false

    var body: some View {

            List(contactStore.contacts) { contact in
                ContactListItem(contact: contact)
            }
            .navigationBarItems(trailing: Button(action: { self.isCreatingNewContact = true }) {
                Image(systemName: "plus")
            })


            .sheet(isPresented: $isCreatingNewContact) {
                NewContactView(didCreateNewContact: self.createNewContactHandler)
        }
    }


    private func createNewContactHandler(_ contact: Contact) {
        contactStore.contacts.append(contact)
        isCreatingNewContact = false
    }

}

编辑 2:内存图截图

编辑 3:奇怪的是,用 VStack 替换 Form 视图可以消除泄漏。如果我使用 List 视图而不是 VStack,泄漏又回来了。

【问题讨论】:

  • 即使在表单中添加了表单,我也无法在提供的代码中获得超过一个 INIT 的输出。但这是一个 INIT,因为您在 NewContactView 初始化期间创建了 ViewModel 的实例。并且没有 DEINIT 因为视图被呈现。请更新问题的真实演示。
  • @Asperi 为了简洁起见,我没有包含显示工作表的代码。我很抱歉。请查看我的编辑。

标签: ios swift swiftui


【解决方案1】:

使用@StateObject,因此它只会初始化一次,对于 iOS 14

永远不要这样做:@ObservedObject private var viewModel = ViewModel(),它会一直创建视图模型。 您需要在应用程序开始时创建此对象并将其传递到不会重新创建的位置,或者您可以在某些不会更新的视图中创建,因为 Dashboard 取决于您的应用程序结构。

【讨论】:

  • 这仅适用于 iOS 14+。它在 iOS 13 中不可用。
【解决方案2】:

看起来没有泄漏。这是我的视图设置(在屏幕上添加了标题以便于参考 - 代码在解释之后)。

导航

User Setup -> Contact Related -> Add Contact

然后一直返回到主屏幕User Setup,然后导航到下一个屏幕会释放先前创建的视图模型实例。注意:我将唯一 id 添加到模型中,当模型被初始化和取消初始化时打印 id。

这让我相信,一旦使用它的视图被释放,被观察的模型就不会被定义。 Swift 运行时控制何时应该释放可观察模型。此外,当我对废弃内存进行分析时,我没有看到内存增长有任何增加。此外,当视图模型的先前实例被释放时,内存中会出现下降,这证明了它正在被释放。

struct ContentView: View {
    var body: some View {
        
        NavigationView {
        VStack {
             NavigationLink(destination: ContactRelated() ) {
                 Text("Contact Related")
             }
             Spacer()
        }
        .navigationBarTitle("User Setup")
        }
    }
}
struct ContactRelated: View {
        
    var body: some View {
    
        VStack {
             NavigationLink(destination: NewContactView() ) {
                 Text("Add New Contact")
             }
             Spacer()
        }
        .navigationBarTitle("Contact")
    }
}
struct NewContactView: View {
    
    class ViewModel: ObservableObject {
        
        var id = UUID()
        
        @Published var firstName = ""
        @Published var lastName = ""
        @Published var email = ""
        @Published var phoneNumber = ""
        
        init() {
            print(">>>init \(id)")
        }
        
        deinit {
            print(">>>deinit \(id)")
        }
        
    }
    
    @ObservedObject private var viewModel = ViewModel()
    
    //var didCreateNewContact: (Contact) -> Void = { _ in }
    
    var body: some View {
        
        Form {
            
            Section(header: Text("Names")) {
                TextField("First Name", text: $viewModel.firstName)
                TextField("Last Name", text: $viewModel.lastName)
            }
            
            Section(header: Text("Details")) {
                TextField("Email", text: $viewModel.email)
                TextField("Phone Number", text: $viewModel.phoneNumber)
            }
            
            Button(action: {}) {
                Text("Save")
            }
        }
    .navigationBarTitle("Add Contact")
        
    }
}

一般我不会在结构体中添加可观察视图模型的定义。

【讨论】:

  • 我修改了我的代码以使用NavigationLink 而不是.sheet,实际上它的行为就像你说的那样。但是,如果您使用工作表显示创建联系人视图,它似乎永远不会释放任何 ViewModel 实例。请查看我的编辑,其中包含工作表演示代码。
  • 你是说定义还是初始化?如果是定义,它只是为了名称间距的目的,但没有必要在视图中嵌套定义它。初始化,因为这是此特定视图的视图模型,因此该视图具有对其自己的视图模型的引用。
【解决方案3】:

这似乎是一个错误。使用 ScrollView 而不是 List 对我有用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-12
    • 2020-05-10
    • 1970-01-01
    • 1970-01-01
    • 2021-06-15
    相关资源
    最近更新 更多