View.environmentObject(_:) 作为该视图的祖先可能会丢失——但并非总是如此……

Posted

技术标签:

【中文标题】View.environmentObject(_:) 作为该视图的祖先可能会丢失——但并非总是如此……【英文标题】:A View.environmentObject(_:) may be missing as an ancestor of this view - but not always… 【发布时间】:2021-12-10 16:40:04 【问题描述】:

我在生产中遇到此错误,无法找到重现它的方法。

致命错误 > 未找到 PurchaseManager 类型的 ObservableObject。一种 PurchaseManager 的 View.environmentObject(_:) 可能作为 这种观点的始祖。 > PurchaseManager > SwiftUI

崩溃来自这个观点:

struct PaywallView: View 
    @EnvironmentObject private var purchaseManager: PurchaseManager
    var body: some View 
        // Call to purchaseManager causing the crash
    

并且这个视图在 MainView 的子视图中实例化

@main
struct MyApp: App 
    let purchasesManager = PurchaseManager.shared
    var body: some Scene 
        WindowGroup 
            MainView()
                .environmentObject(purchasesManager)
            
        
    

或者,当从 UIKit 控制器调用时,从这个控制器:

final class PaywallHostingController: UIHostingController<AnyView> 
    init() 
        super.init(rootView:
                    AnyView(
                        PaywallView()
                            .environmentObject(PurchaseManager.shared)
                    )
        )
    
    @objc required dynamic init?(coder aDecoder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    

我测试了所有触发 PaywallView 显示的用例,我从未遇到过崩溃。

FWIW,PurchaseManager 看起来像这样:

public class PurchaseManager: ObservableObject    
    static let shared       = PurchaseManager()
    init() 
        setupRevenueCat()
        fetchOfferings()
        refreshPurchaserInfo()
    

为什么 ObservableObject 会丢失?在什么情况下?

【问题讨论】:

可能是因为private。或者你应该这样做:.environmentObject(PurchaseManager.shared) 请添加你是如何实现PurchaseManager.shared的代码 @Asperi 为什么?你在找什么? @Asperi 完成。我添加了实现。 可能你的PurchaseManagerinit中的一些函数(比如fetchOfferings)是异步的。这可能是您的问题间歇性出现的原因。 【参考方案1】:

你的问题是间歇性的,可能是因为PurchaseManager init() 可以完成 在正确设置所有数据之前,由于“延迟” init() 中的异步函数。所以有时数据会可用 当视图需要它时,有时它会不存在并导致您的应用崩溃。

您可以尝试以下方法,其中包括使用 @atultw 的建议 状态对象。

import SwiftUI

@main
struct TestApp: App 
    @StateObject var purchaseManager = PurchaseManager() // <-- here
    
    var body: some Scene 
        WindowGroup 
            MainView()
                .onAppear 
                    purchaseManager.startMeUp()    // <-- here
                
                .environmentObject(purchaseManager)
        
    


struct MainView: View 
    @EnvironmentObject var purchaseManager: PurchaseManager
    
    var body: some View 
        Text("testing")
        List 
            ForEach(purchaseManager.offerings, id: \.self)  offer in
                Text(offer)
            
        
    


public class PurchaseManager: ObservableObject 
    @Published var offerings: [String] = []
    
    // -- here --
    func startMeUp() 
        // setupRevenueCat()
        fetchOfferings()
        // refreshPurchaserInfo()
    
    
    func fetchOfferings() 
        DispatchQueue.main.asyncAfter(deadline: .now()+2) 
            self.offerings = ["offer 1","offer 2","offer 3","offer 4"]
        
    

【讨论】:

1/ 由于产品有默认值,数据如何设置不正确?和 2/ 为什么是最后期限? Fwiw,当应用程序从 UIKit 重写为 SwiftUI 时,单例是简化事情的临时妥协。虽然我也不喜欢它,但我似乎看不出它与这次崩溃有关的任何明确原因。 很难回答您的问题,当我们没有您问题的可重现示例代码时,我所能做的就是推测。是的,产品有一个默认值,但假设您正在使用索引进行循环,然后由于异步函数,索引发生变化,它可能会产生问题。您能否说明导致PaywallView 崩溃的原因,我猜可能是MainView。最好的办法是显示一个显示您的问题的示例代码。请注意,在我的简单测试中,单例对我来说不是主要问题。 deadline 只是一个简单的异步函数模拟,需要一些时间才能完成,因为您没有显示 fetchOfferings() 中的内容 感谢@workingdod。我没有更多的工作要做,但你给了我很多。我认为 fetchOfferings/refreshPurchaserInfo 协同工作的方式存在问题。另外,我发现了 environmentObjects 的另一个问题,还有这个 useyourloaf.com/blog/swiftui-environment-when-presenting-views。【参考方案2】:

这里尽量不要使用单例模式(.shared),EnvironmentObject 是为了替代它。您应该在 MyApp 中实例化 PurchasesManager。

@main
struct MyApp: App 
    @StateObject var purchasesManager = PurchaseManager() 
    var body: some Scene 
        WindowGroup 
            MainView()
                .environmentObject(purchasesManager)
            
        
    

没有状态对象可以很好地编译,但如果您希望子视图自动更新,则需要。

用一个虚拟的 PurchasesManager 做这些事情对我来说很好。

【讨论】:

不是我需要的答案,但我同意它应该是一个单例,所以这仍然很有帮助并且值得一票。谢谢

以上是关于View.environmentObject(_:) 作为该视图的祖先可能会丢失——但并非总是如此……的主要内容,如果未能解决你的问题,请参考以下文章

英语高手请进

简易先进先出队列-自用

函数参数

多重背包

合租房合同模板

启动代码分析 02