Существует два базовых c шаблона:
При работе с сетевыми запросами RESTful мы даем всем нашим сетевым подпрограммам закрытие обработчика завершения, которое мы вызываем при запросе сети сделано. Таким образом, вызывающая сторона может вызывать каждый последующий шаг в обработчике завершения предыдущего шага.
Существует много вариантов этой темы (асинхронные Operation
подклассы, фьючерсы / обещания и т. Д. c), но Идея та же, а именно - объединение серии асинхронных задач таким образом, чтобы вызывающий мог знать, когда все запросы были выполнены, и запускать обновление пользовательского интерфейса.
На с другой стороны, при работе с Firestore мы можем добавлять наблюдателей / слушателей для обновления нашего пользовательского интерфейса по мере поступления обновлений. Закрытие addSnapshotListener
неоднократно вызывается при обновлении базовой базы данных. В этом сценарии нет момента «хорошо, мы закончили, обновите пользовательский интерфейс» (поэтому мы обычно не будем использовать подход обработчика завершения), но мы просто будем постоянно обновлять пользовательский интерфейс по мере поступления документов .
Но, хотя ваш пример использует addSnapshotListener
, он также использует limit(to:)
, который добавляет складку. Это немного похоже на первый сценарий (например, если вы ограничены 8, и вы получили 8, слушатель больше не будет вызываться). Но это также немного похоже на второй сценарий (например, если ограничение до 8, и в настоящее время у вас есть только 7 сообщений, он извлечет первые семь и вызовет это закрытие; но если появится другая запись, он снова вызовет закрытие, это время с документом 8 th ).
Попытка обрабатывать как ограниченные / разбитые на страницы ответы, так и прослушивание обновлений в реальном времени может быть затруднено. Я мог бы предложить, чтобы, если вы хотите, чтобы Firestore действовал как служба RESTful, я мог бы предложить использовать getDocuments
вместо addSnapshotListener
, устраняя эту сложность. Затем вы можете использовать подход обработчика завершения, рекомендованный другими. Это делает его немного похожим на подход RESTful (но опять же, вы теряете функцию обновления в реальном времени).
В случае, если вам интересно, как может выглядеть второй сценарий в реальном времени, здесь Это упрощенный пример (мой пост имеет только свойства «текст» и «дата», но, надеюсь, он иллюстрирует процесс):
func addPostsListener() {
db.collection("posts").addSnapshotListener { [weak self] snapshot, error in
guard let self = self else { return }
guard let snapshot = snapshot, error == nil else {
print(error ?? "Unknown error")
return
}
for diff in snapshot.documentChanges {
let document = diff.document
switch diff.type {
case .added: self.add(document)
case .modified: self.modify(document)
case .removed: self.remove(document)
}
}
}
}
func add(_ document: QueryDocumentSnapshot) {
guard let post = post(for: document) else { return }
let indexPath = IndexPath(item: self.posts.count, section: 0)
posts.append(post)
tableView.insertRows(at: [indexPath], with: .automatic)
}
func modify(_ document: QueryDocumentSnapshot) {
guard let row = row(for: document) else { return }
guard let post = post(for: document) else { return }
posts[row] = post
tableView.reloadRows(at: [IndexPath(row: row, section: 0)], with: .automatic)
}
func remove(_ document: QueryDocumentSnapshot) {
guard let row = row(for: document) else { return }
posts.remove(at: row)
tableView.deleteRows(at: [IndexPath(row: row, section: 0)], with: .automatic)
}
func row(for document: QueryDocumentSnapshot) -> Int? {
posts.firstIndex {
$0.id == document.documentID
}
}
func post(for document: QueryDocumentSnapshot) -> Post? {
let data = document.data()
guard
let text = data["text"] as? String,
let timestamp = data["date"] as? Timestamp
else {
return nil
}
return Post(id: document.documentID, text: text, date: timestamp.dateValue())
}
Но этот подход работает, потому что я не ограничиваю ответы. Если вы используете limit(to:)
или limit(toLast:)
, вы перестанете получать обновления в реальном времени, когда достигнете этого предела.