Статьи

Начало работы с Cloud Firestore для iOS

Мобильные кодеры уже много лет используют базу данных Firebase Realtime базы данных Google Mobile Backend as Service (MBaaS), помогая им сосредоточиться на создании функций для своих приложений, не беспокоясь о внутренней инфраструктуре и базе данных. Облегчая хранение и хранение данных в облаке и заботясь об аутентификации и безопасности, Firebase позволяет кодировщикам сосредоточиться на стороне клиента.

В прошлом году Google анонсировал еще одно серверное решение для баз данных, Cloud Firestore , созданное с нуля с обещанием большей масштабируемости и интуитивности. Однако это привело к некоторой путанице относительно его места по отношению к уже существующему флагманскому продукту Google, базе данных Firebase Realtime . В этом уроке будут изложены различия между двумя платформами и отличительные преимущества каждой из них. Вы узнаете, как работать со ссылками на документы Firestore, а также читать, писать, обновлять и удалять данные в режиме реального времени, создав простое приложение для напоминаний.

Это руководство познакомит вас с Cloud Firestore . Вы узнаете, как использовать платформу для сохранения и синхронизации базы данных в реальном времени. Мы рассмотрим следующие темы:

  • что такое Cloud Firestore
  • модель данных Firestore
  • настройка Cloud Firestore
  • создание и работа со ссылками Cloud Firestore
  • чтение данных в режиме реального времени из Cloud Firestore
  • создание, обновление и удаление данных
  • фильтрация и составные запросы

В этом руководстве предполагается, что у вас есть опыт работы с Firebase и фон, развивающийся с помощью Swift и Xcode.

Как и база данных Firebase Realtime , Firestore предоставляет разработчикам мобильных приложений и веб-приложений кросс-платформенное облачное решение для сохранения данных в режиме реального времени, независимо от задержки в сети или подключения к Интернету, а также бесшовной интеграции с набором продуктов Google Cloud Platform. Наряду с этим сходством, есть определенные преимущества и недостатки, которые отличают одно от другого.

На фундаментальном уровне база данных реального времени хранит данные в виде одного большого монолитного иерархического дерева JSON, а Firestore организует данные в документах и ​​коллекциях, а также в подколлекциях. Это требует меньше денормализации. Хранение данных в одном дереве JSON имеет преимущества простоты, когда речь идет о работе с простыми требованиями к данным; однако, это становится более громоздким в масштабе при работе с более сложными иерархическими данными.

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

База данных реального времени поддерживает только ограниченные возможности сортировки и фильтрации — вы можете сортировать или фильтровать только на уровне свойств, но не оба одновременно, в одном запросе. Запросы также глубоки, что означает, что они возвращают большое поддерево результатов назад. Продукт поддерживает только простые операции записи и транзакции, которые требуют завершения обратного вызова.

Firestore, с другой стороны, вводит индексные запросы с составной сортировкой и фильтрацией, позволяя комбинировать действия для создания цепных фильтров и сортировки. Вы также можете выполнять поверхностные запросы, возвращающие под-коллекции вместо всей коллекции, которую вы получили бы с базой данных реального времени. Транзакции являются атомарными по своему характеру, независимо от того, отправляете ли вы пакетную операцию или одну операцию, транзакции повторяются автоматически до тех пор, пока не будут завершены. Кроме того, база данных Realtime поддерживает только отдельные транзакции записи, тогда как Firestore предоставляет пакетные операции атомарно.

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

Другим недостатком базы данных реального времени является ограничение в 100 000 одновременно работающих пользователей (100 000 одновременных подключений и 1000 операций записи в секунду в одной базе данных), после чего вам потребуется разделить базу данных (разделить базу данных на несколько баз данных) для поддержки большего количества пользователей. , Firestore автоматически масштабируется на несколько экземпляров без необходимости вмешательства.

Разработанный с нуля с учетом масштабируемости, Firestore имеет новую схематическую архитектуру, которая реплицирует данные в нескольких регионах, заботится об аутентификации и решает другие вопросы, связанные с безопасностью, все в своем клиентском SDK. Его новая модель данных более интуитивна, чем Firebase, более похожа на другие сопоставимые решения для баз данных NoSQL, такие как MongoDB, при этом обеспечивая более надежный механизм запросов.

Наконец, база данных Realtime, как вы знаете из наших предыдущих руководств, управляет безопасностью посредством каскадных правил с отдельными триггерами проверки. Это работает с правилами базы данных Firebase , проверяя ваши данные отдельно. Firestore, с другой стороны, предоставляет более простую, но более мощную модель безопасности, использующую преимущества облачных правил безопасности Firestore и Identity and Access Management (IAM) , с проверкой данных, выполняемой автоматически.

  • Мобильная разработка
    Правила безопасности Firebase
    Чике Мгбемена

Firestore — это база данных NoSQL на основе документов, состоящая из наборов документов, каждый из которых содержит данные. Поскольку это база данных NoSQL, вы не получите таблицы, строки и другие элементы, которые вы найдете в реляционной базе данных, а вместо этого наборы пар ключ / значение, которые вы найдете в документах.

Вы неявно создаете документы и коллекции, присваивая данные документу, и если документ или коллекция не существуют, они будут созданы автоматически для вас, поскольку коллекция всегда должна быть корневым (первым) узлом. Вот простой пример схемы Tasks для проекта, над которым вы будете работать в ближайшее время, состоящий из коллекции Tasks, а также многочисленных документов, содержащих два поля, имя (строку) и флаг того, выполнена ли задача (логическое значение) ,

Простая задача пример схемы проекта

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

Синонимы таблиц базы данных в мире SQL, коллекции содержат один или несколько документов. Коллекции должны быть корневыми элементами в вашей схеме и могут содержать только документы, но не другие коллекции. Однако вы можете обратиться к документу, который, в свою очередь, относится к коллекциям (подколлекциям).

Схема документа и коллекции

На приведенной выше диаграмме задача состоит из двух примитивных полей (имя и выполнено), а также подколлекции (подзадача), которая состоит из двух собственных примитивных полей.

Документы состоят из пар ключ / значение, причем значения имеют один из следующих типов:

  • примитивные поля (такие как строки, числа, логические)
  • сложные вложенные объекты (списки или массивы примитивов)
  • суб-коллекции

Вложенные объекты также называются картами и могут быть представлены в документе следующим образом. Ниже приведен пример вложенного объекта и массива соответственно:

1
2
3
4
5
6
7
8
9
ID: 2422892 //primitive
name: “Remember to buy milk”
detail: //nested object
    notes: «This is a task to buy milk from the store»
    created: 2017-04-09
    due: 2017-04-10
done: false
notify: [«2F22-89R2», «L092-G623», «H00V-T4S1»]

Для получения дополнительной информации о поддерживаемых типах данных см. Документацию Google Types . Далее вы настроите проект для работы с Cloud Firestore.

Если вы работали с Firebase раньше, многое из этого должно быть вам знакомо. В противном случае вам нужно будет создать учетную запись в Firebase и следовать инструкциям в разделе «Настройка проекта» нашего предыдущего руководства « Начало работы с аутентификацией Firebase для iOS» .

Чтобы следовать этому уроку, клонируйте репозиторий проекта урока . Затем включите библиотеку Firestore добавив следующее в ваш Podfile :

1
2
pod ‘Firebase/Core’
pod ‘Firebase/Firestore’

Введите следующее в свой терминал, чтобы построить свою библиотеку:

1
pod install

Затем переключитесь на Xcode и откройте файл .xcworkspace . Перейдите к файлу AppDelegate.swift и введите в application:didFinishLaunchingWithOptions: method:

В вашем браузере перейдите на консоль Firebase и выберите вкладку База данных слева.

Вкладка базы данных в консоли Firebase

Убедитесь, что вы выбрали опцию « Запуск в тестовом режиме», чтобы у вас не возникало проблем с безопасностью во время эксперимента, и учитывайте уведомление о безопасности, когда вы переводите свое приложение в производство. Теперь вы готовы создать коллекцию и несколько образцов документов.

Для начала создайте исходную коллекцию Tasks , нажав кнопку Add Collection и присвоив ей имя, как показано ниже:

называя коллекцию

Для первого документа вы оставите Идентификатор документа пустым, что автоматически сгенерирует для вас идентификатор. Документ будет просто состоять из двух полей: name и done .

Документ с двумя полями

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

сбор и документ с автоматически сгенерированным идентификатором

С базой данных, настроенной с образцом документа в облаке, вы готовы начать внедрять Firestore SDK в XCode.

Откройте файл MasterViewController.swift в XCode и добавьте следующие строки для импорта библиотеки:

1
2
3
4
5
6
7
8
9
import Firebase
 
class MasterViewController: UITableViewController {
    @IBOutlet weak var addButton: UIBarButtonItem!
     
    private var documents: [DocumentSnapshot] = []
    public var tasks: [Task] = []
    private var listener : ListenerRegistration!
   …

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Foundation
 
struct Task{
    var name:String
    var done: Bool
    var id: String
     
    var dictionary: [String: Any] {
        return [
            «name»: name,
            «done»: done
        ]
    }
}
 
extension Task{
    init?(dictionary: [String : Any], id: String) {
        guard let name = dictionary[«name»] as?
            let done = dictionary[«done»] as?
            else { return nil }
         
        self.init(name: name, done: done, id: id)
    }
}

Приведенный выше фрагмент кода содержит вспомогательное свойство (словарь) и метод (init), которые облегчат заполнение объекта модели. Вернитесь к контроллеру представления и объявите глобальную переменную-установщик, которая ограничит базовый запрос до 50 лучших записей в списке задач. Вы также будете удалять прослушиватель после установки переменной запроса, как didSet свойстве didSet ниже:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
fileprivate func baseQuery() -> Query {
        return Firestore.firestore().collection(«Tasks»).limit(to: 50)
    }
     
    fileprivate var query: Query?
        didSet {
            if let listener = listener {
                listener.remove()
            }
        }
    }
 
override func viewDidLoad() {
        super.viewDidLoad()
        self.query = baseQuery()
    }
 
 override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        self.listener.remove()
    }

При наличии ссылки на документ в viewWillAppear(_animated: Bool) созданный ранее прослушиватель с результатами моментального снимка запроса и получите список документов. Это делается путем вызова query?.addSnapshotListener метода Firestore query?.addSnapshotListener :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
self.listener = query?.addSnapshotListener { (documents, error) in
           guard let snapshot = documents else {
               print(«Error fetching documents results: \(error!)»)
               return
           }
            
           let results = snapshot.documents.map { (document) -> Task in
               if let task = Task(dictionary: document.data(), id: document.documentID) {
                   return task
               } else {
                   fatalError(«Unable to initialize type \(Task.self) with dictionary \(document.data())»)
               }
           }
            
           self.tasks = results
           self.documents = snapshot.documents
           self.tableView.reloadData()
            
       }

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

Чтобы отобразить результаты, заполните следующее TableView методы делегата:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
override func numberOfSections(in tableView: UITableView) -> Int {
       return 1
   }
    
   override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
       return tasks.count
   }
    
    
   override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
       let cell = tableView.dequeueReusableCell(withIdentifier: «Cell», for: indexPath)
        
       let item = tasks[indexPath.row]
        
       cell.textLabel!.text = item.name
       cell.textLabel!.textColor = item.done == false ?
        
       return cell
   }

На этом этапе соберите и запустите проект, и в симуляторе вы сможете наблюдать за данными, появляющимися в режиме реального времени. Добавьте данные через консоль Firebase, и вы должны увидеть, что они мгновенно появляются в симуляторе приложения.

Данные появляются в симуляторе приложения

После успешного прочтения контента из серверной части, затем вы будете создавать, обновлять и удалять данные. Следующий пример иллюстрирует, как обновить данные, используя надуманный пример, в котором приложение позволяет только пометить элемент как выполненный, нажав на ячейку. Обратите внимание на свойство закрытия collection.document( ).updateData(["done": !item.done]) , которое просто ссылается на определенный идентификатор документа, обновляя каждое из полей в словаре:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
override func tableView(_ tableView: UITableView,
                           didSelectRowAt indexPath: IndexPath) {
 
       let item = tasks[indexPath.row]
       let collection = Firestore.firestore().collection(«Tasks»)
 
       collection.document(item.id).updateData([
           «done»: !item.done,
           ]) { err in
               if let err = err {
                   print(«Error updating document: \(err)»)
               } else {
                   print(«Document successfully updated»)
               }
       }
 
       tableView.reloadRows(at: [indexPath], with: .automatic)
        
   }

Чтобы удалить элемент, вызовите метод document( ).delete() :

01
02
03
04
05
06
07
08
09
10
11
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        return true
    }
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
 
        if (editingStyle == .delete){
            let item = tasks[indexPath.row]
            _ = Firestore.firestore().collection(«Tasks»).document(item.id).delete()
        }
 
    }

Создание новой задачи будет включать добавление новой кнопки в вашу раскадровку и подключение ее IBAction к контроллеру представления, создав addTask(_ sender:) . Когда пользователь нажимает кнопку, он выводит на экран лист предупреждения, где пользователь может добавить новое имя задачи:

1
2
3
collection(«Tasks»).addDocument
   (data: [«name»: textFieldReminder.text ??
       «empty task», «done»: false])

Завершите финальную часть приложения, введя следующее:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@IBAction func addTask(_ sender: Any) {
        
       let alertVC : UIAlertController = UIAlertController(title: «New Task», message: «What do you want to remember?», preferredStyle: .alert)
        
       alertVC.addTextField { (UITextField) in
            
       }
        
       let cancelAction = UIAlertAction.init(title: «Cancel», style: .destructive, handler: nil)
        
       alertVC.addAction(cancelAction)
        
       //Alert action closure
       let addAction = UIAlertAction.init(title: «Add», style: .default) { (UIAlertAction) -> Void in
            
           let textFieldReminder = (alertVC.textFields?.first)!
            
           let db = Firestore.firestore()
           var docRef: DocumentReference?
           docRef = db.collection(«Tasks»).addDocument(data: [
               «name»: textFieldReminder.text ??
               «done»: false
           ]) { err in
               if let err = err {
                   print(«Error adding document: \(err)»)
               } else {
                   print(«Document added with ID: \(docRef!.documentID)»)
               }
           }
            
       }
    
       alertVC.addAction(addAction)
       present(alertVC, animated: true, completion: nil)
        
   }

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

Сбор и документы в консоли

До сих пор вы работали только с простым запросом, без каких-либо специальных возможностей фильтрации. Чтобы создать чуть более надежные запросы, вы можете фильтровать по определенным значениям, используя предложение whereField :

1
docRef.whereField(“name”, isEqualTo: searchString)

Вы можете упорядочить и ограничить данные запроса, используя методы order(by: ) и limit(to: ) 🙂 следующим образом:

1
docRef.order(by: «name»).limit(5)

В приложении FirebaseDo вы уже использовали limit с базовым запросом. В приведенном выше фрагменте вы также использовали другую функцию — составные запросы, в которых и порядок, и предел связаны друг с другом. Вы можете связать столько запросов, сколько хотите, например, в следующем примере:

1
2
3
4
5
docRef
   .whereField(“name”, isEqualTo: searchString)
   .whereField(“done”, isEqualTo: false)
   .order(by: «name»)
   .limit(5)

В этом руководстве вы изучили новый продукт Google MBaaS, Cloud Firestore , и в процессе создали простое приложение с напоминанием о задачах, которое демонстрирует, как легко сохранять, синхронизировать и запрашивать данные в облаке. Вы узнали о структуре схемы данных Firestore по сравнению с базой данных Firebase Realtime , а также о том, как читать и записывать данные в режиме реального времени, а также обновлять и удалять данные. Вы также узнали, как выполнять простые и сложные запросы и как фильтровать данные.

Облачное хранилище пожаров было создано с целью обеспечения надежности базы данных Firebase Realtime без многих ограничений, которые должны были перенести мобильные разработчики, особенно в том, что касается масштабируемости и запросов. Мы лишь слегка коснулись того, что вы можете сделать с помощью Firestore, и, безусловно, стоит изучить некоторые из более сложных концепций, таких как разбиение на страницы данных с помощью курсоров запросов , управление индексами и защита ваших данных .