SwiftUI 闭包捕获 @State 会破坏记忆?

urS*_*Sus 3 ios swift swiftui

有 swiftui 失效专家吗?

我知道视图结构初始值设定项应该一直被调用,但只有当输入更改时才调用主体= swiftui 优化

struct FooView : View {
    let counter: Int
    let onOpenFaq: () -> Void
    @State showDialog = false

    var body: some View {
        ...
        .sheet(isPresented: $showDialog) {
            SimpleSheetContent(
                title: "Bar",
                message: "Bar messagge",
                onPositiveClick: onOpenFaq,
                onNegativeClick: {
                    showDialog = false <------------------
                }
            )
        }
    }
}

struct SimpleSheetContent : View {
    let title: String
    let message: String
    let onPositiveClick: () -> Void
    let onNegativeClick: () -> Void

    var body: some View {
        let _ = print("SimpleSheetContent body")
        VStack {
            ...
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我注意到,如果单击闭包showDialog以这种方式写入,那么bodySimpleSheetContent每次更改时都会调用ofcounter - 一个不相关的更改

为什么是这样?这样的闭包不被认为是“稳定的”吗?

//

如果这样写闭包

onNegativeClick: { [$showFoo] in
  $showFoo.wrappedValue = false
}
Run Code Online (Sandbox Code Playgroud)

那么工作表主体不会被调用,一切都会按预期显示

这是一个错误吗?还是我用错了?

rob*_*off 6

counter变化时,SwiftUI 必须再次调用FooView\xe2\x80\x99s bodyFooView\xe2\x80\x99sbody每次调用时都会创建一个新的闭包,并将新的闭包作为SimpleSheetContent.init参数传递给onNegativeClick

\n

然后,SwiftUI 将新SimpleSheetContent值与先前SimpleSheetContent值进行比较。由于SimpleSheetContent不是Equatable,SwiftUI 会逐个比较它。

\n

由于闭包是 never Equatable,SwiftUI\xe2\x80\x99s 唯一的选择是将旧onNegativeClick闭包与新onNegativeClick闭包逐字节进行比较。闭包存储为两个指针:一个指向机器代码的指针,以及一个指向closure\xe2\x80\x99s捕获块的指针,该捕获块通常是堆上的值。

\n

旧闭包和新闭包在堆上的不同地址都有自己的捕获块,因此它们永远不会比较相等。SimpleSheetContent因此 SwiftUI每次都必须询问它的主体。

\n

请注意,SwiftUI 不知道捕获块有多大,或者即使它\xe2\x80\x99 确实是指向捕获块的指针。(Swift ABI 仅要求它是可以传递给swift_retain和 的东西swift_release,并且还有其他东西,例如这些函数可以理解的标记值。)因此 SwiftUI 无法按字节比较捕获块。

\n

更新

\n

您的onNegativeClick关闭只会忽略该工作表。您无需创建闭包即可获得相同的行为。

\n

一种方法是使用dismiss环境的属性:

\n
struct SimpleSheetContent : View {\n    let title: String\n    let message: String\n    let onPositiveClick: () -> Void\n\n    // Replace onNegativeClick with this property: <-----------------------\n    @Environment(\\.dismiss) var dismiss\n\n    var body: some View {\n        let _ = print("SimpleSheetContent body")\n        VStack {\n            ...\n\n            // Use it like this: <----------------------------\n            Button("Cancel") { dismiss() }\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

在我的少量测试中,这可以防止 SwiftUIbody重复调用。

\n

另一种方法是传入Bindingto showDialog

\n
struct SimpleSheetContent : View {\n    let title: String\n    let message: String\n    let onPositiveClick: () -> Void\n\n    // Replace onNegativeClick with this property: <-----------------------\n    @Binding var showDialog: Bool\n\n    var body: some View {\n        let _ = print("SimpleSheetContent body")\n        VStack {\n            ...\n\n            // Use it like this: <----------------------------\n            Button("Cancel") {\n                showDialog = false\n            }\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

在我的少量测试中,这也阻止了 SwiftUI 调用body重复调用。

\n