ios - View 关闭后 ObservedObject View 模型仍在内存中

标签 ios swift swiftui combine

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

例如,如果我有一个 NavigationView,然后导航到带有 TextField 的详细 View ,并在 TextField 中输入一个值并点击后退按钮,那么下次我转到该 View 时,TextField 将具有先前输入的值。

我注意到 View 模型在详细 View 被关闭后仍在内存中,这可能就是 TextField 仍然保持值的原因。

在 UIKit 中,当关闭 ViewController 时, View 模型将被释放,然后在 ViewController 出现时再次创建。这似乎不是这里的情况。

我为此问题附上了一些最低限度的可复制代码。

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 之后添加另一个 View ,比如说 SomeOtherView,那么每次我从 OtherView 输入 TextField 时,都会调用 SomeOtherView 的 View 模型中的 deinit。谁能解释一下为什么会这样?

最佳答案

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 中的交叉引用, 这里是固定变体
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()
    }
}
--
更新:

is there a way to instantiate OtherView when navigating?


这是一个解决方案(使用 Xcode 11.4/iOS 13.4 测试),但这只是成功的一半,因为一旦创建它就会一直存在,直到导航链接重新验证(即在后面它会保留在内存中直到下一次导航)
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
}
backup

关于ios - View 关闭后 ObservedObject View 模型仍在内存中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62474871/

相关文章:

ios - 如何在 swift 3 中以编程方式选择 collectionView 中的单元格?

ios - UITableView 中的最后一个单元格获取第一个单元格的属性

datepicker - 修复来自 DatePicker 的数百行警告重新布局约束

swift - SwiftUI 列表的性能问题

ios - 如何在 Storyboard 中的选项卡栏上启动导航 Controller 的第三个 View Controller

ios - 在 Swift 中单击按钮而不释放触摸

javascript - 如何检查浏览器是否支持真正的触控

ios - Realm.io 数组创建

ios - 当 BLE 设备在范围内时在后台启动应用程序

ios - 如何在 SwiftUI 中为列表创建按字母顺序排列的部分索引?