解析器 SwiftUI 导致崩溃 - 调用的顺序是啥?

Posted

技术标签:

【中文标题】解析器 SwiftUI 导致崩溃 - 调用的顺序是啥?【英文标题】:Resolver SwiftUI Causing Crash - What Order Do Things Get Called?解析器 SwiftUI 导致崩溃 - 调用的顺序是什么? 【发布时间】:2022-01-08 13:32:59 【问题描述】:

我正在开发 SwiftUI 并使用 Resolver 进行依赖注入。作为后端,我使用的是 Firebase。我创建了一个 AuthSession 文件来处理我的所有用户身份验证内容。在该项目中,我还有许多其他存储库,它们在整个应用程序中填充数据。在 AuthSession 中,我为每个存储库创建属性,以便我可以在登录和注销时启动和停止 Firestore 侦听器。在其中几个存储库中,我想通过@InjectedObject 访问 AuthSession,这样当用户登录时,我可以收到通知并可以通过 Combine 获取更新。我的问题是,当我启动应用程序时,它会因奇怪的 Firebase 错误而崩溃。

AuthSession.swift

class AuthSession: ObservableObject 
    let db = Firestore.firestore()
    
    var offerRepository: OfferRepository = Resolver.resolve()
    
    var handle: AuthStateDidChangeListenerHandle?
    
    @Published var currentUser: User?
    @Published var loggedIn = false
    @Published var currentUserUid = ""
    
    // Combine Cancellable
    private var cancellables = Set<AnyCancellable>()
    
    // Intitalizer
    init() 
        
    
    
    func listen() 
        print("AuthSession - listen called")
        // Monitor Authentication chagnes using Firebase Auth.
        handle = Auth.auth().addStateDidChangeListener (auth, user) in
            // Check to see if a user is returned from a sign in or sign up event.
            if let user = user 
                // Set loggedIn to true. This will also be set when a new User is created in SignUpView.
                print("User Exists.")
                self.loggedIn = true
                self.currentUserUid = user.uid
                self.currentUser = user
             else 
                print("Not logged in")
            
        
    

下面是 OfferRepository。添加下面的行时,它会崩溃。如果删除该行,它不会崩溃。我不确定为什么。不包括组合代码。

导致崩溃的线路。

@InjectedObject var authSession: AuthSession

OfferRepository.swift

class OfferRepository: ObservableObject 
    let db = Firestore.firestore()
    private var snapshotListener: ListenerRegistration?
    
    @InjectedObject var authSession: AuthSession

    @Published var offers = [Offer]()
    
    private var cancellables = Set<AnyCancellable>()
    
    init() 
        startSnapshotListener()
    
        
    func startSnapshotListener() 
        if snapshotListener == nil 
            self.snapshotListener = db.collection(FirestoreCollection.offers).order(by: "created", descending: true).addSnapshotListener  (querySnapshot, error) in
                if let error = error 
                    print("Error getting documents: \(error)")
                 else 
                    guard let documents = querySnapshot?.documents else 
                        print("No Offers.")
                        return
                    
                    
                    self.offers = documents.compactMap  offer in
                        do 
                            return try offer.data(as: Offer.self)
                         catch 
                            print(error)
                        
                        return nil
                    
                
            
        
        
    

这里是我的 AppDelegate+Registering 文件供参考。

extension Resolver: ResolverRegistering 
    public static func registerAllServices() 
        register  AuthSession() .scope(.application)
        register  OfferRepository() as OfferRepository .scope(.application)
    

应用程序在 Firestore 包中的以下行崩溃。

- (NSString *)keyForDatabase:(NSString *)database 
  return [NSString stringWithFormat:@"%@|%@", self.app.name, database];

线程 1:EXC_BAD_ACCESS(代码=2,地址=0x16d317ff8)

虽然我可以在登录和注销视图中启动和停止侦听器,但我更愿意将其保留在 AuthSession 文件中。有没有办法解决这个问题?

【问题讨论】:

【参考方案1】:

@InjectedObject 旨在用于将ObservableObjects 注入 SwiftUI 视图 - 请参阅文档:https://github.com/hmlongco/Resolver#property-wrappers

由于您想在存储库中引用AuthenticationService(即ObservableObjects,您应该改用@Injected

这是来自我的一个应用程序的 sn-p:

public class ArtifactRepository: ObservableObject 
  // MARK: - Dependencies
  @Injected var db: Firestore
  @Injected var authenticationService: AuthenticationService
  
  // MARK: - Publishers
  @Published public var artifacts = [Artifact]()
  
  // MARK: - Private attributes
  private var statusFilter: Status
  private var userId: String = "unknown"
  private var listenerRegistration: ListenerRegistration?
  private var cancellables = Set<AnyCancellable>()
  
  let logger = Logger(subsystem: "dev.peterfriese.App", category: "persistence")
  
  public init(statusFilter: Status = .inbox, liveSync: Bool = true) 
    // filtering
    self.statusFilter = statusFilter
    
    // observe user ID
    authenticationService.$user
      .compactMap  user in
        user?.uid
      
      .assign(to: \.userId, on: self)
      .store(in: &cancellables)
    
    // if live sync is on, (re)load data when user changes
    if liveSync 
      authenticationService.$user
        .receive(on: DispatchQueue.main)
        .sink  [weak self] user in
          if self?.listenerRegistration != nil 
            self?.unsubscribe()
            self?.subscribe()
          
        
        .store(in: &cancellables)
    
  
  
  deinit 
    unsubscribe()
  
  
  public func unsubscribe() 
    if listenerRegistration != nil 
      listenerRegistration?.remove()
      listenerRegistration = nil
    
  
  
  public func subscribe() 
    if listenerRegistration == nil 
      
      var query = db.collection("artifacts")
        .whereField("userId", isEqualTo: self.userId)
      
      if (statusFilter != .all) 
        query = query.whereField("status", isEqualTo: statusFilter.rawValue)
      
      
      listenerRegistration = query.order(by: "dateAdded", descending: true)
        .addSnapshotListener  [weak self] (querySnapshot, error) in
          guard let documents = querySnapshot?.documents else 
            self?.logger.debug("No documents")
            return
          
          
          self?.logger.debug("Mapping \(documents.count) documents")
          self?.artifacts = documents.compactMap  queryDocumentSnapshot in
            try? queryDocumentSnapshot.data(as: Artifact.self)
          
        
    
  

public class AuthenticationService: ObservableObject 
  private let logger = Logger(subsystem: "dev.peterfriese.App", category: "authentication")
  
  @Published public var user: User?
  
  private var handle: AuthStateDidChangeListenerHandle?
  
  public init() 
    setupKeychainSharing()
    registerStateListener()
  
  
  public func signIn() 
    if Auth.auth().currentUser == nil 
      Auth.auth().signInAnonymously()
    
  
  
  public func signOut() 
    do 
      try Auth.auth().signOut()
    
    catch 
      print("error when trying to sign out: \(error.localizedDescription)")
    
  
  
  private let accessGroup = "XXXXXXX.dev.peterfriese.App"
  
  private func setupKeychainSharing() 
    do 
      let auth = Auth.auth()
      auth.shareAuthStateAcrossDevices = true
      try auth.useUserAccessGroup(accessGroup)
    
    catch let error as NSError 
      print("Error changing user access group: %@", error)
    
  
  
  private func registerStateListener() 
    if handle == nil 
      handle = Auth.auth().addStateDidChangeListener( (auth, user) in
        self.user = user
        
        if let user = user 
          if user.isAnonymous 
            self.logger.debug("User signed in anonymously with user ID \(user.uid).")
          
          else 
            self.logger.debug("User signed in with user ID \(user.uid). Email: \(user.email ?? "(empty)"), display name: [\(user.displayName ?? "(empty)")]")
          
        
        else 
          self.logger.debug("User signed out.")
          self.signIn()
        
      )
    
  

【讨论】:

一读到这篇文章,我就意识到 InjectedObject 是为视图设计的。感谢您的帮助。 我认为这是答案,但崩溃仍然发生。这似乎很奇怪。在您的示例中,您没有尝试在 AuthenticationService 中解析 A​​rtifactRepository。我相信这是问题所在。 我刚刚注意到你的代码中似乎有一个依赖循环:AuthSession 依赖于 OfferRepository(见var offerRepository: OfferRepository = Resolver.resolve() 行),而 OfferRepository 依赖于 AuthSession(见@InjectedObject var authSession: AuthSession 行)。我敢打赌,如果您查看调用堆栈,您会注意到堆栈溢出。您的 AuthSession 永远不应该依赖于您的存储库 - 反之亦然。 太棒了。谢谢。

以上是关于解析器 SwiftUI 导致崩溃 - 调用的顺序是啥?的主要内容,如果未能解决你的问题,请参考以下文章

共享 SwiftUI 视图的屏幕截图导致崩溃

为啥@FocusState 会导致 SwiftUI 预览崩溃

SwiftUI 应用程序在两个选择器周围出现 VStack 崩溃

从 Javascript 调用包装器函数会导致应用程序崩溃 - React-Native

Swift之深入解析SwiftUI属性包装器如何处理结构体

Xcode编写SwiftUI代码时一个编译通过但导致预览(Preview)崩溃的小陷阱