Запросить несколько ключей на Firebase

Я слежу за рекомендациями Firebase по выравниванию данных, но у меня возникли проблемы с перечислением ряда элементов из моей базы данных.

Вот пример моего файла базы данных:

"users" : {
    "UID12349USER" : {
      "firstName" : "Jon",
      "lastName" : "Snow",
      "email" : "[email protected]",
      "albums" : {
        "UID124ALBUM" : true,
        "UID125ALBUM" : true
      }
    }
},
"albums" : {
    "UID124ALBUM" : {
      "name" : "My Artwork",
    },
    "UID125ALBUM" : {
      "name" : "My Sketches",
    }
}

Я получаю список альбомов для данного пользователя:

let userAlbums = database.child(usersKey).child(user.uid).child(albumsKey)
userAlbums.observeSingleEventOfType(.Value, withBlock: { snapshot in
    // fetch [UID124ALBUM: 1, UID125ALBUM: 1]
})

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

for key in albumKeys {
    let album = database.child(self.albumsKey).child(key)
    album.observeSingleEventOfType(.Value, withBlock: { snapshot in
        // fetch album.. append to array
    })
}

Использование этого подхода затрудняет обнаружение завершенных запросов из-за асинхронного характера запросов. Добавьте к этому тот факт, что некоторые из запросов могут завершиться неудачно из-за плохого соединения.

Кроме того, если я хочу отфильтровать один из альбомов с заданным именем (например, "Мои работы" ) или вернуть нуль, если он не существует, я также получаю сложное конечное условие.

var found = false

for key in albumKeys {
    let album = database.child(self.albumsKey).child(key)
    album.observeSingleEventOfType(.Value, withBlock: { snapshot in
        // if current.name == "My Artwork"
        // completion(current)
    })
}
// This block will be called before observeSingleEventOfType =.=
if !found {
    completion(nil)
}

У меня хороший фон для iOS и Swift, но я знаю базы данных Firebase и NoSQL. Может ли кто-нибудь указать мне хорошее направление? Должен ли я разорвать Firebase и попробовать что-то еще? Я пропустил какой-то метод, который может запросить то, что мне нужно? Является ли моя json-структура неправильной и не хватает некоторых дополнительных ключей?

Спасибо

Ответ 1

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

Например, в вашем случае: -

let userAlbums = database.child(usersKey).child(user.uid).child(albumsKey)

userAlbums.observeSingleEventOfType(.Value, withBlock: { snapshot in
 if(snapshot.exists()) {
      // fetch [UID124ALBUM: 1, UID125ALBUM: 1] in the albumKeys
        for key in albumKeys {
          let album = database.child(self.albumsKey).child(key)
          album.observeSingleEventOfType(.Value, withBlock: { snapshot in 
                   // fetch album.. append to array
         })
        }
 }
})

Теперь, чтобы отфильтровать один из ваших альбомов, вы можете использовать его как функцию внутри функции:

 var found = false
 let userAlbums = database.child(usersKey).child(user.uid).child(albumsKey)

 userAlbums.observeSingleEventOfType(.Value, withBlock: { snapshot in
     if(snapshot.exists()) {
        // fetch [UID124ALBUM: 1, UID125ALBUM: 1] in the albumKeys
        for key in albumKeys {
          let album = database.child(self.albumsKey).child(key)
          album.observeSingleEventOfType(.Value, withBlock: { snapshot in 
                   // if current.name == "My Artwork"
                   found = true
                   completion(current)
         })
        }
  }
 })
 if !found {
    completion(nil)
 }

Ответ 2

Я новичок как в iOS, так и в firebase, поэтому принимайте мое решение с солью. Это обходной путь, и он может быть не герметичным.

Если я правильно понял ваш вопрос, я столкнулся с подобной проблемой. У вас есть "пользователи" и "альбомы". У меня есть "пользователи" и "globalWalls". Внутри "пользователей" у меня есть "wallBuiltByUser", который содержит ключи от глобальных таблиц.

Мне захотелось пройти через wallBuiltByUser и получить их соответствующие node из globalWalls. В конце концов, мне нужно позвонить моему делегату с уведомлением о том, что стены были восстановлены.

Я считаю, что это может быть похоже на то, что вы пытаетесь сделать.

Вот мое решение:

       databaseRef.child("users/\(userID)/WallsBuiltByUser/").observeSingleEvent(of: FIRDataEventType.value, with: { (snapshot:FIRDataSnapshot) in


        let numOfWalls = Int(snapshot.childrenCount)
        var wallNum:Int = 0

        for child in snapshot.children {

            let wallId = (child as! FIRDataSnapshot).key

            self.databaseRef.child("globalWalls").child(wallId).observeSingleEvent(of: .value, with: { (snapshot: FIRDataSnapshot) in

                wallNum = wallNum + 1
                let wallServerInfo = snapshot.value as? NSDictionary!

                if (wallServerInfo != nil){
                    let wall = Wall(wallInfo: wallServerInfo)
                    self.returnedWalls.append(wall)
                }

                if(wallNum == numOfWalls){
                    print("this should be printed last")
                    self.delegate?.retrieved(walls: self.returnedWalls)
                }
            })//end of query of globalWalls


        }//end of for loop


    })//end of query for user walls

Ответ 3

Я бы предложил использовать DispatchGroup и взаимное исключение для обработки асинхронных функций в цикле for. Вот код, который вы предоставили с помощью DispatchGroup чтобы убедиться, что все асинхронные функции в цикле были выполнены до того, как он проверит оператор if:

let myGroup = DispatchGroup()
var found = false
// iterate through your array
for key in albumKeys {
    let album = database.child(self.albumsKey).child(key)
    // lock the group 
    myGroup.enter()
    album.observeSingleEventOfType(.Value, withBlock: { snapshot in
        if current.name == "My Artwork" {
             found = true
        }
        // after the async work has been completed, unlock the group
        myGroup.leave()
    })
}
// This block will be called after the final myGroup.leave() of the looped async functions complete
myGroup.notify(queue: .main) {
    if !found {
        completion(nil)
    }
}

Все, что содержится в myGroup.notify(queue:.main) {, не будет выполняться до myGroup.enter() пор, пока myGroup.enter() и myGroup.leave() не будут вызваны столько же раз. Обязательно вызовите myGroup.leave() в блоке наблюдения Firebase (после работы async) и убедитесь, что он вызывается, даже если ошибка возникает из наблюдения.