Reputation: 379
I am instantiating a User
class via a Firebase DataSnapshot. Upon calling the initializer init(snapshot: DataSnapshot)
, it should asynchronously retrieve values from two database references, namely pictureRef
and nameRef
, via the getFirebasePictureURL
and getFirebaseNameString
methods' @escaping completion handlers (using Firebase's observeSingleEvent
method). The references pictureRef
and nameRef
are both children of a single parent node. However, when instantiating the class, it never initializes the name and picture User
class properties because init
is executed synchronously.
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!)
})
}
}
It was suggested in a previous post that I add an @escaping completion handler to the init
method:
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)
}
}
}
However, this would require that if I initialize this class via User(snapshot: snapshot)
in a method outside of this class that I encapsulate the body of that method within the completion handler of the User
init method, which wouldn't work for my current project.
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?
Upvotes: 1
Views: 368
Reputation: 437872
Is there a way to employ dispatch groups to pause execution on the main thread until the
fullName
andpictureURL
are populated with values? Or is there an alternative way of doing this?
Well, you never want to “pause” execution. But, you can use dispatch groups to notify your app when two asynchronous methods are complete and when it’s now possible to create that new instance:
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))
}
}
And then:
let userId = ...
createUser(for: userId) { user in
// use `User` instance here, e.g. creating your new node
}
Where User
is now simplified:
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
}
}
But I’d advise against trying to bury asynchronous code inside the init
method. Instead, I'd flip it around and create your instance when the two asynchronous methods are done.
Upvotes: 1