Swift Firestore — возврат из функции getDocument()

# #swift #firebase #google-cloud-firestore #combine

Вопрос:

В настоящее время я занимаюсь этой проблемой. У меня есть база данных Firestore. Моя цель-заполнить friends массив User сущностями после извлечения. Я вызываю fetchFriends , который выбирает текущего зарегистрированного пользователя, в котором есть friends массив (каждый элемент-идентификатор друга). friends затем массив зацикливается, извлекается каждый идентификатор друга и создается новая сущность User . Я хочу сопоставить этот friends массив с friends опубликованной переменной. То, что я там сделал, не работает, и я не могу придумать какое-то решение.

База данных Firestore

 User
- name: String
- friends: [String]
 

Модель пользователя

 struct User: Identifiable, Codable {   
    @DocumentID var id: String?
    var name: String?
    var email: String
    var photoURL: URL?
    
    var friends: [String]?
}
 

Модель представления пользователя

 @Published var friends = [User?]()

func fetchFriends(uid: String) {
    
    let userRef = db.collection("users").document(uid)
    
    userRef.addSnapshotListener { documentSnapshot, error in
        
        do {
            guard let user = try documentSnapshot?.data(as: User.self) else {
                return
            }
            
            self.friends = user.friends!.compactMap({ friendUid in
                
                self.fetchUserAndReturn(uid: friendUid) { friend in
                    return friend
                }
            })
        }
        catch {
            print(error)
        }
    }
}

func fetchUserAndReturn(uid: String, callback:@escaping (User)->User) {
    let friendRef = db.collection("users").document(uid)
    
    friendRef.getDocument { document, error in
        callback(try! document?.data(as: User.self) as! User)
    }
}
 

Комментарии:

1. Где именно в этой цепочке событий она обрывается? Поместите print операторы везде и подтвердите, что документ действительно загружен, что User объект фактически инициализирован, что массив фактически зациклен и т. Д. Где-то по пути он будет напечатан неправильно и сообщит нам, где это находится.

2. @MJ-12 Отличное предложение. Я бы также добавил точку останова и прошелся по вашему коду строка за строкой, изучая переменные по пути. Как только вы найдете тот, который не соответствует вашим ожиданиям, обновите этот вопрос, указав, какая это была строка и каким должно было быть ожидаемое значение.

3. Разве основная проблема здесь не в том, что self.friends ожидается заполнение повторяющимися вызовами fetchUserAndReturn , которые также являются асинхронными (или, в частности, getDocument функция внутри них) и не вернут a User в compactMap ожидаемое состояние?

4. в строке self.fetchUserAndReturn я теперь получаю это сообщение об ошибке: «Не удается преобразовать значение типа ‘()’ в тип результата закрытия ‘Пользователь??'»

5. @jnpdx это может быть оно. У вас есть какие-нибудь идеи, как заставить это работать? Я пытался добавлять в массив self.friends каждый раз, когда получал результат, но он странно работал в поле зрения (из — за добавления в массив мне приходилось очищать self.friends каждый раз, когда в базу данных вносились новые изменения, и из-за этого каждая перезагрузка представления сначала очищала все содержимое, а затем заполняла его подвидами-это выглядело не очень хорошо).

Ответ №1:

Один из вариантов-использовать группы диспетчеризации для группирования чтения пользователей, но на самом деле код в вопросе не так уж далек.

На самом деле в compactMap нет необходимости, так как идентификаторы пользователей уникальны, как и идентификаторы документов в одной коллекции, поэтому не должно быть проблем с дублированием идентификаторов пользователей в качестве друзей.

Используя объект пользователя в вопросе, вот как заполнить массив друзей

 func fetchFriends(uid: String) {
    let userRef = db.collection("users").document(uid)
    userRef.addSnapshotListener { documentSnapshot, error in
        guard let user = try! documentSnapshot?.data(as: User.self) else { return }

        user.friends!.forEach( { friendUid in
            self.fetchUserAndReturn(uid: friendUid, completion: { returnedFriend in
                self.friendsArray.append(returnedFriend)
            })
        })
    }
}

func fetchUserAndReturn(uid: String, completion: @escaping ( MyUser ) -> Void ) {
    let userDocument = self.db.collection("users").document(uid)
    userDocument.getDocument(completion: { document, error in
        guard let user = try! document?.data(as: User.self) else { return }
        completion(user)
    })
}
 

Обратите внимание, что я удалил всю проверку ошибок для краткости, поэтому обязательно включите проверку на наличие ошибок Firebase, а также нулевых объектов.