如何在 class 初始化时使用调度组异步等待 Firebase 回调?

How to use dispatch group to asynchronously await Firebase callback upon class initialization?

我正在通过 Firebase DataSnapshot 实例化 User class。在调用初始化程序 init(snapshot: DataSnapshot) 时,它应该通过 getFirebasePictureURLgetFirebaseNameString 方法的 @escaping 从两个数据库引用异步检索值,即 pictureRefnameRef完成处理程序(使用 Firebase 的 observeSingleEvent 方法)。引用 pictureRefnameRef 都是单个父节点的子节点。但是,在实例化class时,它从不初始化名称和图片User class属性,因为init是同步执行的。

import Firebase

class User {

 var uid: String
 var fullName: String? = ""
 var pictureURL: URL? = URL(string: "initial")

//DataSnapshot Initializer

init(snapshot: DataSnapshot) {

self.uid = snapshot.key

getFirebasePictureURL(userId: uid) { (url) in

    self.getFirebaseNameString(userId: self.uid) { (fullName) in

        self.fullName = fullName
        self.profilePictureURL = url

    }
}

func getFirebasePictureURL(userId: String, completion: @escaping (_ url: URL) -> Void) {

    let currentUserId = userId
    //Firebase database picture reference
    let pictureRef = Database.database().reference(withPath: "pictureChildPath")

    pictureRef.observeSingleEvent(of: .value, with: { snapshot in

        //Picture url string
        let pictureString = snapshot.value as! String

        //Completion handler (escaping)
        completion(URL(string: pictureString)!)

    })

}


func getFirebaseNameString(userId: String, completion: @escaping (_ fullName: String) -> Void) {

    let currentUserId = userId
    //Firebase database name reference
    let nameRef = Database.database().reference(withPath: "nameChildPath")

    nameRef.observeSingleEvent(of: .value, with: { snapshot in

        let fullName = snapshot.value as? String

       //Completion handler (escaping)
        completion(fullName!)

        })
     }
  }

有人在 中建议我将 @escaping 完成处理程序添加到 init 方法:

init(snapshot: DataSnapshot, completionHandler: @escaping (User) -> Void) {

self.uid = snapshot.key

getFirebasePictureURL(userId: uid) { (url) in

    self.getFirebaseNameString(userId: self.uid) { (fullName) in

        self.fullName = fullName
        self.profilePictureURL = url

        completionHandler(self)
      }
   }
}

但是,如果我在此 class 之外的方法中通过 User(snapshot: snapshot) 初始化此 class,则需要将该方法的主体封装在完成处理程序中User init 方法,它不适用于我当前的项目。

有没有办法使用调度组来暂停主线程上的执行,直到 fullNamepictureURL 填充了值?还是有其他方法可以做到这一点?

Is there a way to employ dispatch groups to pause execution on the main thread until the fullName and pictureURL are populated with values? Or is there an alternative way of doing this?

好吧,你永远不想“暂停”执行。但是,您可以使用调度组在两个异步方法完成时以及现在可以创建新实例时通知您的应用程序:

func createUser(for userId: String, completion: @escaping (User) -> Void) {
    var pictureUrl: URL?
    var fullName: String?

    let group = DispatchGroup()

    group.enter()
    getFirebaseNameString(userId: userId) { name in
        fullName = name
        group.leave()
    }

    group.enter()
    getFirebasePictureURL(userId: userId) { url in
        pictureUrl = url
        group.leave()
    }

    group.notify(queue: .main) {
        guard
           let pictureUrl = pictureUrl, 
           let fullName = fullName 
        else { return }

        completion(User(uid: userId, fullName: fullName, pictureURL: pictureUrl))
    }
}

然后:

let userId = ...
createUser(for: userId) { user in 
    // use `User` instance here, e.g. creating your new node
}

其中 User 现在已简化:

class User {
    let uid: String
    let fullName: String
    let pictureURL: URL

    init(uid: String, fullName: String, pictureURL: URL) {
        self.uid = uid
        self.fullName = fullName
        self.pictureURL = pictureURL
    }
}

但我建议不要尝试将异步代码隐藏在 init 方法中。相反,我会翻转它并在完成两个异步方法后创建您的实例。