Как использовать диспетчерскую группу для асинхронного ожидания обратного вызова Firebase при инициализации класса? - PullRequest
1 голос
/ 21 сентября 2019

Я создаю экземпляр класса User с помощью Firebase DataSnapshot.При вызове инициализатора init(snapshot: DataSnapshot) он должен асинхронно извлекать значения из двух ссылок на базу данных, а именно pictureRef и nameRef, через обработчики завершения @escaping методов getFirebasePictureURL и getFirebaseNameString (используя метод observeSingleEvent Firebase),Ссылки pictureRef и nameRef являются потомками одного родительского узла.Однако при создании экземпляра класса он никогда не инициализирует свойства имени и изображения User класса, поскольку 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!)

        })
     }
  }

В предыдущем посте *1016* было высказано предположение, чтоЯ добавляю обработчик завершения @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)
      }
   }
}

Однако для этого потребуется, если я инициализирую этот класс с помощью User(snapshot: snapshot) в методе вне этого класса, который инкапсулирует телоэтого метода в обработчике завершения метода User init, который не будет работать для моего текущего проекта.

Есть ли способ использовать группы диспетчеризации для приостановки выполнения в главном потоке до fullName и pictureURL заполнены значениями?Или есть альтернативный способ сделать это?

1 Ответ

1 голос
/ 21 сентября 2019

Есть ли способ использовать группы диспетчеризации, чтобы приостановить выполнение в главном потоке, пока значения fullName и pictureURL не будут заполнены значениями?Или есть альтернативный способ сделать это?

Ну, вы никогда не хотите «приостановить» выполнение.Но вы можете использовать группы рассылки, чтобы уведомить ваше приложение, когда два асинхронных метода завершены и когда теперь возможно создать этот новый экземпляр:

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.Вместо этого я бы перевернул его и создал бы ваш экземпляр, когда два асинхронных метода будут выполнены.

...