视图关闭后,ObservedObject 视图模型仍在内存中 [英] ObservedObject view-model is still in memory after the view is dismissed

查看:16
本文介绍了视图关闭后,ObservedObject 视图模型仍在内存中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在 SwiftUI 和 Combine 中的内存管理方面遇到了一些问题.

I am having some trouble with memory management in SwiftUI and Combine.

例如,如果我有一个 NavigationView,然后使用 TextField 导航到详细视图,并在 TextField 中输入一个值并点击后退按钮,则下次我转到该视图时,TextField 具有先前输入的值.

For example, if I have a NavigationView and then navigate to a detail view with a TextField, and enter a value in the TextField and tap on the back button, next time I go to that view the TextField has the previously entered value.

我注意到在细节视图被关闭后视图模型仍然在内存中,这可能就是 TextField 仍然保留一个值的原因.

I noticed that the view-model is still in memory after the detail view is dismissed, and that's probably why the TextField still holds a value.

在 UIKit 中,当关闭 ViewController 时,视图模型将被释放,然后在 ViewController 呈现时再次创建.这里似乎不是这种情况.

In UIKit, when dismissing a ViewController, the view-model will be deallocated and then created again when the ViewController is presented. This seems to not be the case here.

我为此问题附上了一些最低限度的可重现代码.

I attach some minimum reproductible code for this issue.

import SwiftUI
import Combine

struct ContentView: View {
    var body: some View {
        NavigationView {
            NavigationLink(destination: OtherView()) {
                Text("Press Here")
            }
        }
    }
}

struct OtherView: View {

    @ObservedObject var viewModel = ViewModel()

    var body: some View {
        VStack {
            TextField("Something", text: $viewModel.enteredText)
                .textFieldStyle(RoundedBorderTextFieldStyle())

            Button(action: {
                print("Tap")
            }) {
                Text("Tapping")
            }.disabled(!viewModel.isValid)
        }
    }
}

class ViewModel: ObservableObject {

    @Published var enteredText = ""
    var isValid = false

    var cancellable: AnyCancellable?

    init() {
        cancellable = textValidatedPublisher.receive(on: RunLoop.main)
            .assign(to: .isValid, on: self)
    }

    deinit {
        cancellable?.cancel()
    }

    var textValidatedPublisher: AnyPublisher<Bool, Never> {
        $enteredText.map {
            $0.count > 1
        }.eraseToAnyPublisher()
    }


}

我还注意到,例如,如果我添加另一个视图,假设在OtherView 之后是SomeOtherView,那么每次我从OtherView 输入TextField 时,就会调用来自SomeOtherView 的视图模型的deinit.任何人都可以解释为什么会发生这种情况吗?

I also noticed that, if for example, I add another view, let's say SomeOtherView after OtherView, then each time I type in the TextField from OtherView, then the deinit from SomeOtherView's view-model is called. Can anyone please also explain why this happens?

推荐答案

此外,我注意到如果我对 ContetView 进行更改并且重新评估视图,那么我将在内存中拥有两个 ViewModel

Moreover, I noticed that if I to a change in ContetView and the view is reevaluated, then I will have two ViewModels in memory

这是由于ViewModel中的交叉引用,这里是固定变体

It is due to cross-reference in ViewModel, here is fixed variant

struct OtherView: View, Constructable {

    @ObservedObject var viewModel = ViewModel()

    var body: some View {
        VStack {
            TextField("Something", text: $viewModel.enteredText)
                .textFieldStyle(RoundedBorderTextFieldStyle())

            Button(action: {
                print("Tap")
            }) {
                Text("Tapping")
            }.disabled(!viewModel.isValid)
        }
        .onDisappear {
            self.viewModel.invalidate()     // << here !!
        }
    }
}

class ViewModel: ObservableObject {

    @Published var enteredText = ""
    var isValid = false

    var cancellable: AnyCancellable?

    init() {
        print("[>>] created")
        cancellable = textValidatedPublisher.receive(on: RunLoop.main)
            .assign(to: .isValid, on: self)
    }

    func invalidate() {
        cancellable?.cancel()
        cancellable = nil
        print("[<<] invalidated")
    }

    deinit {
//        cancellable?.cancel()     // not here !!!
        print("[x] done")
    }

    var textValidatedPublisher: AnyPublisher<Bool, Never> {
        $enteredText.map {
            $0.count > 1
        }.eraseToAnyPublisher()
    }
}

--

更新:

有没有办法在导航时实例化OtherView?

is there a way to instantiate OtherView when navigating?

这是一个解决方案(使用 Xcode 11.4/iOS 13.4 测试),但这只是半价,因为一旦创建,它将一直有效,直到导航链接重新验证(即在后面它会保留在内存中,直到下一次导航)

Here is a solution (tested with Xcode 11.4 / iOS 13.4), but this is only half-a-deal, because once created it will be alive until navigation link revalidated (ie. on back it remains in memory until next navigate)

struct ContentView: View {
    var body: some View {
        NavigationView {
            NavigationLink(destination: 
               // create wrapper view with type of view which creation
               // is deferred until navigation
               DeferCreatingView(of: OtherView.self)) {
                Text("Press Here")
            }
        }
    }
}

protocol Constructable {
    init()
}

struct DeferCreatingView<T: View & Constructable>: View {
    var ViewType: T.Type

    init(of type: T.Type) {
        ViewType = type
    }

    var body: some View {
        ViewType.init()     // << create only here
    }
}


struct OtherView: View, Constructable {
    // .. not changed code from first part
}

这篇关于视图关闭后,ObservedObject 视图模型仍在内存中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆