为什么/何时在Swift中为EnvironmentObjects允许使用未初始化的非可选值?

时间:2019-06-07 16:18:47

标签: swift swiftui

在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决定emptyDataemptyData2都应使用相同的对象引用进行初始化?我还可以传递RandomView实例中甚至不存在为变量的其他环境对象:

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

尽管SomeData()实例中未在任何地方使用RandomView(),但SwiftUI还是很高兴地运行,在我看来应该会触发编译时错误。

为什么在初始化对象时在编译时允许未初始化的值而无需对其进行初始化,为什么我们可以自由地传递环境实例而无需对其进行任何处理?在我看来有点像Javascript,我喜欢Swift中强大的静态安全键入...我看不到为什么明智的成员初始化程序只是生成一个将环境变量作为参数的初始化程序。

2 个答案:

答案 0 :(得分:1)

EnvironmentObject属性委托具有一个init()方法,该方法不带任何参数,并且为包装的属性提供了隐式初始化

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

Modern Swift API Design视频中大约在28:10对此进行了解释)。因此,这些(非可选)属性不需要(显式)初始值。

文档还指出EnvironmentObject是(强调)

  

...一个动态视图属性,该属性使用可转换对象的祖先视图提供的可绑定对象来使当前视图无效。

     

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

这就是我的理解:

  • 如果在当前视图或其祖先之一的环境中找到匹配的可绑定对象(在您的情况下为EmptyData的实例),则将属性初始化为此对象。
  • 如果在祖先视图中没有找到匹配的可绑定对象,则程序会因运行时错误而终止。
  • 环境对象可用于视图层次结构中的所有视图,全部视图或全部视图中。 (请参阅29:20的Data Flow Through SwiftUI。)因此提供SomeData中未使用的环境对象(在您的情况下为RandomView的实例)并不是错误。 / li>

答案 1 :(得分:1)

什么是@EnvironmentObject?

  

一个链接的View属性,该属性读取由   祖先

因此,可以从祖先向儿童提供环境道具,而不必从其直接父母那里获得。 这样,请看下面的代码片段,因为RandomViewGrandParent将所需的Env对象注入到环境中,所以如果RandomViewParent的子代需要相同的Env obj,则RandomViewParent无需执行任何操作。 RandomViewParent可以仅启动视图,而无需再次传递env obj。

BindableObject

然后回答您的另一个问题-

我通过EmptyData(),SwiftUI决定emptyData和 应该使用相同的对象引用来初始化emptyData2?

那是因为EnvironmentObject符合BindableObject,而BindableObject的didChange是Publisher,所以我认为它认为emptyData和emptyData2都希望订阅相同的事件/值,因此对两者使用相同的引用。