当从
@State
闭包访问视图的 @Binding
或 onSubmit
时,它会使视图无法取消初始化。基本示例:我们有一个按钮,它可以切换条件以显示带有 onSubmit
修饰符的目标视图。因此,多次点击按钮可以为我们提供以下诊断信息:
[Input] constructed: (1)
[Input] constructed: (2)
[Input] deallocating: (1)
[Input] constructed: (2)
[Input] deallocating: (1)
[Input] constructed: (2)
[Input] deallocating: (1)
我们可以看到:一个实例永远存活!
让我们用
onSubmit
注释行,或者用onTapGesture
替换它,一切都会好起来的:
[Input] constructed: (1)
[Input] deallocating: (0)
[Input] constructed: (1)
[Input] deallocating: (0)
[Input] constructed: (1)
[Input] deallocating: (0)
[Input] constructed: (1)
[Input] deallocating: (0)
代码示例:
import SwiftUI
struct ContentView : View {
@State private var state: Bool = false
var body: some View {
VStack {
Button("push") { state.toggle() }
if state { Input() } else { Color.red }
}
}
}
struct Input : View {
@State private var text: String
@State private var whatever: Bool
private let tracing: InstanceTracer
init() {
self.text = "text"
self.whatever = true
self.tracing = InstanceTracer("Input")
}
var body: some View {
TextField("placeholder", text: $text)
.onSubmit { whatever.toggle() } // <- causes instance unable to deallocate
}
}
final class InstanceTracer {
private static var instances: [String : Int] = [ : ]
private let name: String
init(_ name: String) {
self.name = name
print("[\(name)] constructed: (\(increment(numberOf: name)))")
}
deinit { print("[\(name)] deallocating: (\(decrement(numberOf: name)))") }
private func increment(numberOf name: String) -> Int {
var number: Int = if let number: Int = Self.instances[name] { number } else { 0 }
number += 1
Self.instances[name] = number
return number
}
private func decrement(numberOf name: String) -> Int {
var number: Int = if let number: Int = Self.instances[name] { number } else { 0 }
if number < 1 { return 0 }
number -= 1
Self.instances[name] = number
return number
}
}
避免永生实例的方法是使用
onCommit
的 TextField
参数而不是 onSubmit
修饰符:
var body: some View {
TextField("placeholder", text: $text, onCommit: { whatever.toggle() })
// instead of: TextField("placeholder", text: $text).onSubmit { whatever.toggle() }
}