我在SwiftUI和Combine中的内存管理遇到了麻烦。
例如,如果我有一个NavigationView,然后导航到带有TextField的详细信息视图,然后在TextField中输入一个值,然后单击“后退”按钮,则下次我转到该视图时,TextField具有先前输入的值
我注意到在取消了详细视图后,视图模型仍在内存中,这可能就是TextField仍然保留值的原因。
在UIKit中,当关闭ViewController时,将释放视图模型,然后在显示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之后添加SomeOtherView,那么每次我从OtherView中键入TextField时,就会调用SomeOtherView的视图模型中的deinit。谁能解释一下为什么会这样吗?
答案 0 :(得分:3)
此外,我注意到,如果我对ContetView进行了更改并且对视图进行了重新评估,那么内存中将有两个ViewModels
这是由于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()
}
}
-
更新:
导航时是否可以实例化OtherView?
这是一个解决方案(已通过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
}
答案 1 :(得分:0)
将导航视图样式 @font-face {
font-family: "Gilroy";
src: local("Gilroy"),
url(../fonts/Gilroy-Regular.otf) format("opentype"), // font-weight: 400
url(../fonts/Gilroy-SemiBold.otf) format("opentype"), // font-weight: 600
url(../fonts/Gilroy-Bold.otf) format("opentype"); // font-weight: 700
}
body {
overflow-x: hidden;
font-family: 'Gilroy', sans-serif;
font-display: swap;
}
h1,
h2,
h3,
h4,
h5,
h6,
p {
color: $text-color;
}
添加到导航视图。它将取消初始化视图模型。请参阅下面的修改后的代码。
.navigationViewStyle(StackNavigationViewStyle())