3

在 Swift 中,这会在运行时崩溃:

class EmptyData: BindableObject {
    let didChange = PassthroughSubject<EmptyData, Never>()
}

struct RandomView : View {
    @EnvironmentObject var emptyData: EmptyData
    @EnvironmentObject var emptyData2: EmptyData

    var body: some View {
        Text("Hello World!")
    }
}

并在SceneDelegate.swift

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    let window = UIWindow(frame: UIScreen.main.bounds)
    // The emptyData variables are not initialized as seen below
    window.rootViewController = UIHostingController(rootView: RandomView())
    self.window = window
    window.makeKeyAndVisible()
}

线程 1:EXC_BAD_INSTRUCTION(代码=EXC_I386_INVOP,子代码=0x0)

解决问题并不难,但很奇怪:

window.rootViewController = UIHostingController(rootView: RandomView().environmentObject(EmptyData()))

那么这里发生了什么?我通过EmptyData()了,SwiftUI 决定两者都emptyData应该emptyData2使用相同的对象引用进行初始化?我还可以将其他甚至不存在的环境对象作为RandomView实例中的变量传递:

window.rootViewController = UIHostingController(rootView: RandomView().environmentObject(EmptyData()).environmentObject(SomeData()))

而且 SwiftUI 只是愉快地运行,虽然SomeData()没有在实例中的任何地方使用,RandomView()并且在我看来应该触发编译时错误。

为什么在编译时允许未初始化的值而不在初始化对象时初始化它们,为什么我们可以自由地传递环境实例而不对它们做任何事情?对我来说看起来有点像 Javascript,我喜欢 Swift 中强大的静态安全类型……我不明白为什么成员初始化器只生成一个将环境变量作为参数的初始化器。

4

2 回答 2

1

什么是@EnvironmentObject?

读取BindableObject祖先提供的链接视图属性

因此,环境道具可以从祖先提供给孩子,不一定应该来自其直接父母。有了这个,看看下面的代码片段,因为 RandomViewGrandParent 将所需的 Env 对象注入到环境中,如果 RandomViewParent 的孩子需要相同的 Env obj,则 RandomViewParent 不需要做任何事情。RandomViewParent 可以在不再次传递 env obj 的情况下启动视图。

class EmptyData: BindableObject {
    let didChange = PassthroughSubject<EmptyData, Never>()
}

struct RandomViewGrandParent : View {
    var body: some View {
        RandomViewParent().environmentObject(EmptyData())
    }
}

struct RandomViewParent : View {
    @EnvironmentObject var emptyData: EmptyData
    @EnvironmentObject var emptyData2: EmptyData

    var body: some View {
        RandomView()
    }
}

struct RandomView : View {
    @EnvironmentObject var emptyData: EmptyData
    @EnvironmentObject var emptyData2: EmptyData

    var body: some View {
        Text("Hello World!")
    }
}

并回答您的另一个问题-

我通过 EmptyData() 并且 SwiftUI 决定 emptyData 和 emptyData2 都应该使用相同的对象引用进行初始化?

那是因为 EnvironmentObject 符合 BindableObject 并且 BindableObject 的 didChange 是一个发布者,所以我相信它认为 emptyData 和 emptyData2 都想订阅相同的事件/值,因此对两者使用相同的 ref。

于 2019-06-07T21:14:01.783 回答
1

EnvironmentObject属性委托有一个不带参数的init()方法,它为包装的属性提供隐式初始化

@EnvironmentObject var emptyData: EmptyData
@EnvironmentObject var emptyData2: EmptyData

(这在大约 28:10的Modern Swift API Design视频中进行了解释)。这就是为什么这些(非可选)属性不需要(显式)初始值的原因。

该文档还指出EnvironmentObject(强调)

... 一个动态视图属性,它使用由祖先视图提供的可绑定对象在可绑定对象更改时使当前视图无效。

您必须通过调用其 environmentObject(_:) 方法在祖先视图上设置模型对象。

所以这就是我的理解:

  • EmptyData如果在当前视图或其祖先之一的环境中找到匹配的可绑定对象(在您的情况下: 的实例),则将属性初始化为此对象。
  • 如果在祖先视图中找不到匹配的可绑定对象,则程序以运行时错误终止。
  • 环境对象可用于视图层次结构中的所有视图、部分视图或不使用任何视图。(请参阅29:20通过 SwiftUI的数据流。)因此,提供SomeData未在RandomView.
于 2019-06-07T21:00:02.123 回答