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 完成。我添加了实现。
可能你的PurchaseManager
init
中的一些函数(比如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(_:) 作为该视图的祖先可能会丢失——但并非总是如此……的主要内容,如果未能解决你的问题,请参考以下文章