【问题标题】: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 文件中。有没有办法解决这个问题?

【问题讨论】:

    标签: firebase swiftui resolver


    【解决方案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 永远不应该依赖于您的存储库 - 反之亦然。
    • 太棒了。谢谢。
    猜你喜欢
    • 1970-01-01
    • 2014-06-11
    • 1970-01-01
    • 1970-01-01
    • 2011-08-24
    • 2015-04-29
    • 2012-07-13
    • 2015-11-11
    • 1970-01-01
    相关资源
    最近更新 更多