Мобилните кодери се възползват от платформата Mobile Backend като услуга (MBaaS) на Google Firebase Realtime Database в продължение на много години, като им помагат да се съсредоточат върху изграждането на функции за своите приложения, без да се налага да се притесняват за инфраструктурата и базата данни на задния край. Като улеснява съхраняването и запазването на данни в облака и се грижи за удостоверяването и сигурността, Firebase позволява на кодерите да се съсредоточат върху клиентската страна.
Миналата година Google обяви още едно решение за база данни, Cloud Firestore, изградено от самото начало с обещанието за по-голяма мащабируемост и интуитивност. Това обаче внесе известно объркване относно мястото му във връзка с вече съществуващия водещ продукт на Google, Firebase Realtime Database. Този урок ще очертае разликите между двете платформи и отделните предимства на всяка. Ще научите как да работите с препратките към документи на Firestore, както и да четете, записвате, актуализирате и изтривате данни в реално време, като създадете просто приложение за напомняния.
Цели на този урок
Този урок ще ви изложи на Cloud Firestore. Ще научите как да използвате платформата за постоянство и синхронизация на базата данни в реално време. Ще разгледаме следните теми:
- какво е Cloud Firestore
- моделът на данни на Firestore
- настройване на Cloud Firestore
- създаване и работа с препратки в Cloud Firestore
- четене на данни в реално време от Cloud Firestore
- създаване, актуализиране и изтриване на данни
- филтриране и комбинирани заявки
Предполагаеми знания
Този урок предполага, че сте имали известна експозиция на Firebase и фон, който се разработва със Swift и Xcode.
Какво е Cloud Firestore?
Подобно на Firebase Realtime Database, Firestore предоставя на мобилните и уеб разработчиците облачно решение за различни платформи за запазване на данни в реално време, независимо от мрежовата латентност или интернет свързаност, както и безпроблемна интеграция с пакета продукти на Google Cloud Platform. Наред с тези прилики, има различни предимства и недостатъци, които отличават един от друг.
Модел на данни
На фундаментално ниво базата данни в реално време съхранява данни като едно голямо, монолитно, йерархично JSON дърво, докато Firestore организира данни в документи и колекции, както и подколекции. Това изисква по-малко денормализация. Съхраняването на данни в едно JSON дърво има предимствата на простотата, когато става въпрос за работа с прости изисквания за данни; обаче става по-тромаво в мащаб, когато се работи с по-сложни йерархични данни.
Офлайн поддръжка
И двата продукта предлагат офлайн поддръжка, активно кеширане на данни в опашки, когато има латентна или липсва мрежова свързаност – синхронизиране на локалните промени обратно към задния край, когато е възможно. Firestore поддържа офлайн синхронизация за уеб приложения в допълнение към мобилните приложения, докато базата данни в реално време позволява само мобилно синхронизиране.
Заявки и транзакции
Базата данни в реално време поддържа само ограничени възможности за сортиране и филтриране – можете да сортирате или филтрирате само на ниво собственост, но не и на двете в една заявка. Заявките също са дълбоки, което означава, че връщат обратно голямо поддърво от резултати. Продуктът поддържа само прости операции за запис и транзакции, които изискват обратно извикване за завършване.
Firestore, от друга страна, въвежда индексни заявки със сложно сортиране и филтриране, което ви позволява да комбинирате действия за създаване на верижни филтри и сортиране. Можете също така да изпълнявате плитки заявки, връщайки подколекции вместо цялата колекция, която бихте получили с базата данни в реално време. Транзакциите са атомарни по природа, независимо дали изпращате пакетна операция или единична, като транзакциите се повтарят автоматично до приключване. Освен това базата данни в реално време поддържа само индивидуални транзакции за запис, докато Firestore предоставя групови операции атомарно.
Ефективност и мащабируемост
Базата данни в реално време, както бихте очаквали, е доста стабилна и има ниска латентност. Базите данни обаче са ограничени до отделни региони, в зависимост от зоналната наличност. Firestore, от друга страна, съхранява данни хоризонтално в множество зони и региони, за да гарантира истинска глобална наличност, мащабируемост и надеждност. Всъщност Google обеща, че Firestore ще бъде по-надежден от базата данни в реално време.
Друг недостатък на базата данни в реално време е ограничението до 100 000 едновременни потребители (100 000 едновременни връзки и 1 000 записа в секунда в една база данни), след което ще трябва да разделите вашата база данни (разделите вашата база данни на множество бази данни), за да поддържате повече потребители . Firestore автоматично се мащабира в множество инстанции, без да се налага да се намесвате.
Проектиран от самото начало с предвид мащабируемостта, Firestore има нова схематична архитектура, която репликира данни в множество региони, се грижи за удостоверяването и се занимава с други въпроси, свързани със сигурността, всички в рамките на своя SDK от страна на клиента. Неговият нов модел на данни е по-интуитивен от този на Firebase, като по-близко наподобява други сравними NoSQL решения за бази данни като MongoDB, като същевременно осигурява по-стабилна машина за заявки.
Сигурност
И накрая, базата данни в реално време, както знаете от предишните ни уроци, управлява сигурността чрез каскадни правила с отделни тригери за валидиране. Това работи с Firebase Database Rules, като потвърждава данните ви отделно. Firestore, от друга страна, предоставя по-опростен, но по-мощен модел за сигурност, използващ предимствата на Правилата за сигурност на Cloud Firestore и управление на самоличността и достъпа (IAM), като валидирането на данните се изключва автоматично.
- Mobile DevelopmentFirebase Security RulesChike Mgbemena
Моделът на данни на Firestore
Firestore е базирана на документи NoSQL база данни, състояща се от колекции от документи, всеки от които съдържа данни. Тъй като това е NoSQL база данни, няма да получите таблици, редове и други елементи, които бихте намерили в релационна база данни, а вместо това набори от двойки ключ/стойност, които ще намерите в документи.
Вие създавате документи и колекции имплицитно, като присвоявате данни към документ и ако документът или колекцията не съществуват, те автоматично ще бъдат създадени за вас, тъй като колекцията винаги трябва да бъде основният (първият) възел. Ето една проста примерна схема на Tasks на проекта, върху който ще работите скоро, състояща се от колекцията Tasks, както и множество документи, съдържащи две полета, името (низ) и флаг за това дали задачата е изпълнена (булева) .
Нека разложим всеки един от елементите, за да можете да ги разберете по-добре.
Колекции
Синоним на таблици на база данни в света на SQL, колекциите съдържат един или повече документи. Колекциите трябва да бъдат основни елементи във вашата схема и могат да съдържат само документи, но не и други колекции. Можете обаче да се позовавате на документ, който от своя страна се отнася до колекции (подколекции).
В диаграмата по-горе една задача се състои от две примитивни полета (име и готово), както и подколекция (подзадача), която се състои от две собствени примитивни полета.
Документи
Документите се състоят от двойки ключ/стойност, като стойностите имат един от следните типове:
- примитивни полета (като низове, числа, булеви)
- сложни вложени обекти (списъци или масиви от примитиви)
- подколекции
Вложените обекти се наричат още карти и могат да бъдат представени, както следва, в рамките на документа. Следва пример за вложен обект и масив, съответно:
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 за типовете данни. След това ще настроите проект за работа с Cloud Firestore.
Настройване на проекта
Ако сте работили с Firebase преди, много от това трябва да ви е познато. В противен случай ще трябва да създадете акаунт във Firebase и да следвате инструкциите в раздела „Настройване на проекта“ на предишния ни урок Първи стъпки с удостоверяване на Firebase за iOS .
За да следвате този урок, клонирайте репото на проекта за урок. След това включете библиотеката Firestore от добавяне на следното към вашия Podfile :
pod 'Firebase/Core' pod 'Firebase/Firestore'
Въведете следното във вашия терминал, за да изградите своята библиотека:
pod install
След това превключете към Xcode и отворете .xcworkspace файл. Придвижете се до AppDelegate.swift файл и въведете следното в application:didFinishLaunchingWithOptions:
метод:
FirebaseApp.configure()
В браузъра си отворете конзолата Firebase и изберетеБаза данни раздел вляво.
Уверете се, че сте избрали опцията за Стартиране в тестов режим така че да нямате проблеми със сигурността, докато експериментираме, и да обърнете внимание на известието за сигурност, когато преместите приложението си в производство. Вече сте готови да създадете колекция и някои примерни документи.
Добавяне на колекция и примерен документ
За да започнете, създайте първоначална колекция, Tasks
, като изберете Добавяне на колекция бутон и именуване на колекцията, както е показано по-долу:
За първия документ ще оставите празен идентификатор на документа, който автоматично ще генерира идентификатор за вас. Документът просто ще се състои от две полета: name
и done
.
Запазете документа и трябва да можете да потвърдите събирането и документа заедно с автоматично генерирания идентификатор:
С базата данни, настроена с примерен документ в облака, вие сте готови да започнете да внедрявате Firestore SDK в Xcode.
Създаване и работа с препратки към база данни
Отворете MasterViewController.swift файл в Xcode и добавете следните редове, за да импортирате библиотеката:
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 , което ще представлява вашия модел на данни:
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? String, let done = dictionary["done"] as? Bool else { return nil } self.init(name: name, done: done, id: id) } }
Кодовият фрагмент по-горе включва свойство за удобство (речник) и метод (init), които ще улеснят попълването на обекта на модела. Превключете обратно към контролера на изглед и декларирайте глобална променлива за настройка, която ще ограничи основната заявка до първите 50 записа в списъка със задачи. Вие също така ще премахнете слушателя, след като зададете променливата на заявката, както е посочено в didSet
свойство по-долу:
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() }
Четене на данни в реално време от Cloud Firestore
С препратката към документа на място, в viewWillAppear(_animated: Bool)
, свържете слушателя, който сте създали по-рано, с резултатите от моментната снимка на заявката и извлечете списък с документи. Това става чрез извикване на метода Firestore query?.addSnapshotListener
:
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
делегирани методи:
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 ? UIColor.black : UIColor.lightGray return cell }
На този етап изградете и стартирайте проекта и в симулатора трябва да можете да наблюдавате данните, които се появяват в реално време. Добавете данни през конзолата на Firebase и трябва да видите, че се появяват незабавно в симулатора на приложението.
Създаване, актуализиране и изтриване на данни
След успешно четене на съдържание от бек-енда, след това ще създавате, актуализирате и изтривате данни. Следващият пример ще илюстрира как да актуализирате данни, като използваме измислен пример, при който приложението ще ви позволи да маркирате елемент като направен само чрез докосване на клетката. Обърнете внимание на collection.document(
item.id
).updateData(["done": !item.done])
closure property, което просто препраща към конкретен идентификатор на документ, актуализирайки всяко от полетата в речника:
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(
item.id
).delete()
метод:
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() } }
Създаването на нова задача ще включва добавяне на нов бутон във вашия Storyboard и свързване на неговия IBAction
към контролера на изглед, създавайки addTask(_ sender:)
метод. Когато потребител натисне бутона, той ще изведе предупредителен лист, където потребителят може да добави ново име на задача:
collection("Tasks").addDocument (data: ["name": textFieldReminder.text ?? "empty task", "done": false])
Завършете последната част на приложението, като въведете следното:
@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)! as UITextField let db = Firestore.firestore() var docRef: DocumentReference? = nil docRef = db.collection("Tasks").addDocument(data: [ "name": textFieldReminder.text ?? "empty task", "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
клауза:
docRef.whereField(“name”, isEqualTo: searchString)
Можете да поръчате и ограничите данните си от заявката, като използвате order(by: )
и limit(to: )
методи, както следва:
docRef.order(by: "name").limit(5)
В приложението FirebaseDo вече сте използвали limit
с основната заявка. В горния фрагмент също използвахте друга функция, сложни заявки, където и поръчката, и ограничението са свързани заедно. Можете да верижите толкова заявки, колкото искате, като например в следния пример:
docRef .whereField(“name”, isEqualTo: searchString) .whereField(“done”, isEqualTo: false) .order(by: "name") .limit(5)