Mobilní kodéři již mnoho let využívají platformu Firebase Realtime Database od společnosti Google Mobile Backend as a Service (MBaaS), která jim pomáhá soustředit se na vytváření funkcí pro jejich aplikace, aniž by se museli starat o back-endovou infrastrukturu a databázi. Tím, že Firebase usnadňuje ukládání a uchovávání dat v cloudu a stará se o ověřování a zabezpečení, umožňuje kodérům soustředit se na stranu klienta.
Minulý rok Google oznámil další back-end databázové řešení, Cloud Firestore, postavené od základů s příslibem větší škálovatelnosti a intuitivnosti. To však způsobilo určitý zmatek, pokud jde o jeho místo ve vztahu k již existující vlajkové lodi společnosti Google, Firebase Realtime Database. Tento tutoriál nastíní rozdíly mezi těmito dvěma platformami a výrazné výhody každé z nich. Vytvořením jednoduché aplikace pro připomenutí se naučíte pracovat s referencemi dokumentů Firestore a také číst, zapisovat, aktualizovat a mazat data v reálném čase.
Cíle tohoto kurzu
Tento výukový program vás seznámí s Cloud Firestore. Dozvíte se, jak využít platformu pro perzistenci a synchronizaci databáze v reálném čase. Budeme se zabývat následujícími tématy:
- co je Cloud Firestore
- datový model Firestore
- nastavení Cloud Firestore
- vytváření referencí Cloud Firestore a práce s nimi
- čtení dat v reálném čase z Cloud Firestore
- vytváření, aktualizace a mazání dat
- filtrování a složené dotazy
Předpokládané znalosti
Tento tutoriál předpokládá, že jste měli určitou zkušenost s Firebase a máte zkušenosti s vývojem pomocí Swift a Xcode.
Co je Cloud Firestore?
Stejně jako Firebase Realtime Database i Firestore poskytuje mobilním a webovým vývojářům cloudové řešení pro více platforem pro uchování dat v reálném čase, bez ohledu na latenci sítě nebo připojení k internetu, a také bezproblémovou integraci se sadou produktů Google Cloud Platform. Spolu s těmito podobnostmi existují výrazné výhody a nevýhody, které jeden od druhého odlišují.
Datový model
Na základní úrovni Realtime Database ukládá data jako jeden velký, monolitický, hierarchický strom JSON, zatímco Firestore organizuje data v dokumentech a kolekcích a také v podsbírkách. To vyžaduje méně denormalizace. Ukládání dat do jednoho stromu JSON má výhody jednoduchosti, pokud jde o práci s jednoduchými požadavky na data; při práci se složitějšími hierarchickými daty se však ve velkém měřítku stává těžkopádnější.
Podpora offline
Oba produkty nabízejí offline podporu, aktivně ukládají data do mezipaměti ve frontách, když existuje latentní nebo žádné síťové připojení – synchronizace místních změn zpět do back-endu, je-li to možné. Firestore podporuje offline synchronizaci pro webové aplikace kromě mobilních aplikací, zatímco databáze v reálném čase umožňuje pouze mobilní synchronizaci.
Dotazy a transakce
Databáze v reálném čase podporuje pouze omezené možnosti třídění a filtrování – v jediném dotazu můžete třídit nebo filtrovat pouze na úrovni vlastností, ale ne obojí. Dotazy jsou také hluboké, což znamená, že vracejí zpět velký podstrom výsledků. Produkt podporuje pouze jednoduché operace zápisu a transakce, které vyžadují zpětné volání dokončení.
Firestore na druhou stranu zavádí indexové dotazy se složeným tříděním a filtrováním, což umožňuje kombinovat akce a vytvářet řetězové filtry a řazení. Můžete také provádět mělké dotazy vracející podsbírky namísto celé kolekce, kterou byste získali s Realtime Database. Transakce jsou atomické povahy, ať už odesíláte dávkovou operaci nebo jednu operaci, přičemž transakce se automaticky opakují, dokud nejsou uzavřeny. Realtime Database navíc podporuje pouze jednotlivé transakce zápisu, zatímco Firestore umožňuje dávkové operace atomicky.
Výkon a škálovatelnost
Databáze v reálném čase, jak byste očekávali, je poměrně robustní a má nízkou latenci. Databáze jsou však omezeny na jednotlivé oblasti v závislosti na zónové dostupnosti. Firestore na druhé straně uchovává data horizontálně napříč více zónami a regiony, aby byla zajištěna skutečná globální dostupnost, škálovatelnost a spolehlivost. Google ve skutečnosti slíbil, že Firestore bude spolehlivější než databáze v reálném čase.
Dalším nedostatkem databáze v reálném čase je omezení na 100 000 souběžných uživatelů (100 000 souběžných připojení a 1 000 zápisů/sekundu v jedné databázi), po kterém byste museli databázi rozdělit (rozdělit databázi do více databází), abyste mohli podporovat více uživatelů. . Firestore se automaticky škáluje napříč více instancemi, aniž byste museli zasahovat.
Firestore, navržený od základů s ohledem na škálovatelnost, má novou schematickou architekturu, která replikuje data ve více oblastech, stará se o autentizaci a zpracovává další záležitosti související se zabezpečením, to vše v rámci sady SDK na straně klienta. Jeho nový datový model je intuitivnější než Firebase, více se podobá jiným srovnatelným NoSQL databázovým řešením, jako je MongoDB, a zároveň poskytuje robustnější dotazovací modul.
Zabezpečení
A konečně, databáze v reálném čase, jak víte z našich předchozích tutoriálů, spravuje zabezpečení pomocí kaskádových pravidel se samostatnými spouštěči ověřování. Funguje to s pravidly databáze Firebase, která ověřují vaše data samostatně. Firestore na druhé straně poskytuje jednodušší, ale výkonnější model zabezpečení využívající pravidla zabezpečení Cloud Firestore a správu identity a přístupu (IAM), s automatickou výjimkou ověřování dat.
- Mobile DevelopmentFirebase Security RulesChike Mgbemena
Datový model Firestore
Firestore je databáze založená na NoSQL dokumentech, sestávající z kolekcí dokumentů, z nichž každý obsahuje data. Jelikož se jedná o NoSQL databázi, nezískáte tabulky, řádky a další prvky, které byste našli v relační databázi, ale místo toho sady párů klíč/hodnota, které byste našli v dokumentech.
Dokumenty a kolekce vytváříte implicitně přiřazením dat k dokumentu, a pokud dokument nebo kolekce neexistuje, bude automaticky vytvořena za vás, protože kolekce musí být vždy kořenovým (prvním) uzlem. Zde je jednoduché schéma příkladu Tasks projektu, na kterém budete brzy pracovat, sestávající z kolekce Tasks a také mnoha dokumentů obsahujících dvě pole, název (řetězec) a příznak, zda je úkol dokončen (boolean) .
Pojďme si každý z prvků rozložit, abyste jim lépe porozuměli.
Sbírky
Kolekce, které jsou synonymem pro databázové tabulky ve světě SQL, obsahují jeden nebo více dokumentů. Kolekce musí být kořenovými prvky vašeho schématu a mohou obsahovat pouze dokumenty, nikoli jiné kolekce. Můžete však odkazovat na dokument, který zase odkazuje na sbírky (podsbírky).
Ve výše uvedeném diagramu se úkol skládá ze dvou primitivních polí (jméno a hotovo) a také z podsbírky (dílčího úkolu), která se skládá ze dvou vlastních primitivních polí.
Dokumenty
Dokumenty se skládají z párů klíč/hodnota, přičemž hodnoty mají jeden z následujících typů:
- primitivní pole (jako jsou řetězce, čísla, logická hodnota)
- složité vnořené objekty (seznamy nebo pole primitiv)
- podsbírky
Vnořené objekty se také nazývají mapy a mohou být v dokumentu znázorněny následovně. Následuje příklad vnořeného objektu a pole:
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"] ...
Další informace o podporovaných typech dat naleznete v dokumentaci k datovým typům společnosti Google. Dále nastavíte projekt pro práci s Cloud Firestore.
Nastavení projektu
Pokud jste již dříve pracovali s Firebase, mnoho z toho by vám mělo být známé. V opačném případě si budete muset vytvořit účet ve Firebase a postupovat podle pokynů v části „Nastavit projekt“ v našem předchozím kurzu Začínáme s ověřováním Firebase pro iOS .
Chcete-li pokračovat v tomto výukovém programu, naklonujte repo projektu výukového programu. Dále zahrňte knihovnu Firestore od přidání následujícího do vašeho Podsouboru :
pod 'Firebase/Core' pod 'Firebase/Firestore'
Pro vytvoření knihovny zadejte do svého terminálu následující:
pod install
Dále přepněte na Xcode a otevřete .xcworkspace soubor. Přejděte na AppDelegate.swift a do pole application:didFinishLaunchingWithOptions:
zadejte následující metoda:
FirebaseApp.configure()
V prohlížeči přejděte do konzole Firebase a vyberte Databáze kartu vlevo.
Ujistěte se, že jste vybrali možnost Spustit v testovacím režimu abyste během experimentování neměli žádné problémy se zabezpečením a při přesunu aplikace do produkce dbejte na bezpečnostní upozornění. Nyní jste připraveni vytvořit kolekci a několik vzorových dokumentů.
Přidání sbírky a vzorového dokumentu
Chcete-li začít, vytvořte úvodní kolekci Tasks
, výběrem možnosti Přidat sbírku a pojmenování kolekce, jak je znázorněno níže:
U prvního dokumentu necháte ID dokumentu prázdné, čímž se ID automaticky vygeneruje za vás. Dokument se bude jednoduše skládat ze dvou polí: name
a done
.
Uložte dokument a měli byste být schopni potvrdit sbírku a dokument spolu s automaticky vygenerovaným ID:
S databází nastavenou pomocí vzorového dokumentu v cloudu jste připraveni začít implementovat Firestore SDK v Xcode.
Vytvoření a práce s databázovými referencemi
Otevřete soubor MasterViewController.swift soubor v Xcode a přidejte následující řádky pro import knihovny:
import Firebase class MasterViewController: UITableViewController { @IBOutlet weak var addButton: UIBarButtonItem! private var documents: [DocumentSnapshot] = [] public var tasks: [Task] = [] private var listener : ListenerRegistration! ...
Zde jednoduše vytváříte proměnnou posluchače, která vám umožní spustit připojení k databázi v reálném čase, když dojde ke změně. Vytváříte také DocumentSnapshot
odkaz, který bude obsahovat dočasný snímek dat.
Než budete pokračovat s ovladačem zobrazení, vytvořte další soubor swift, Task.swift , který bude reprezentovat váš datový model:
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) } }
Výše uvedený fragment kódu obsahuje vlastnost pohodlí (slovník) a metodu (init), které usnadní naplnění objektu modelu. Přepněte zpět na řadič pohledu a deklarujte proměnnou globálního nastavení, která omezí základní dotaz na 50 nejlepších položek v seznamu úkolů. Jakmile nastavíte proměnnou dotazu, odeberete také posluchač, jak je uvedeno v didSet
vlastnost níže:
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() }
Čtení dat v reálném čase z Cloud Firestore
S odkazem na dokument v viewWillAppear(_animated: Bool)
, přiřaďte dříve vytvořený posluchač k výsledkům snímku dotazu a načtěte seznam dokumentů. To se provádí voláním metody 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() }
Výše uvedený uzávěr přiřazuje snapshot.documents
iterativním mapováním pole a jeho zabalením do nového Task
objekt instance modelu pro každou datovou položku ve snímku. Takže pomocí několika řádků jste úspěšně načetli všechny úkoly z cloudu a přiřadili je ke globálním tasks
pole.
Chcete-li zobrazit výsledky, vyplňte následující TableView
delegovat metody:
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 }
V této fázi sestavte a spusťte projekt a v simulátoru byste měli být schopni sledovat data objevující se v reálném čase. Přidejte data prostřednictvím konzole Firebase a měli byste je okamžitě vidět v simulátoru aplikace.
Vytváření, aktualizace a mazání dat
Po úspěšném načtení obsahu z back-endu dále vytvoříte, aktualizujete a odstraníte data. Následující příklad bude ilustrovat, jak aktualizovat data, pomocí vymyšleného příkladu, kdy vám aplikace umožní označit položku jako hotovou pouze klepnutím na buňku. Všimněte si collection.document(
item.id
).updateData(["done": !item.done])
vlastnost uzavření, která jednoduše odkazuje na konkrétní ID dokumentu a aktualizuje každé z polí ve slovníku:
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) }
Chcete-li odstranit položku, zavolejte document(
item.id
).delete()
metoda:
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() } }
Vytvoření nového úkolu bude zahrnovat přidání nového tlačítka do Storyboardu a připojení jeho IBAction
k řadiči zobrazení a vytvoření addTask(_ sender:)
metoda. Když uživatel stiskne tlačítko, zobrazí se list s upozorněním, kde může uživatel přidat nový název úkolu:
collection("Tasks").addDocument (data: ["name": textFieldReminder.text ?? "empty task", "done": false])
Dokončete poslední část aplikace zadáním následujícího:
@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) }
Sestavte a spusťte aplikaci ještě jednou, a když se objeví simulátor, zkuste přidat několik úkolů a také některé označit jako dokončené a nakonec otestujte funkci odstranění odstraněním některých úkolů. Chcete-li potvrdit, že uložená data byla aktualizována v reálném čase, přepněte se do své databáze Firebase a sledujte sbírku a dokumenty.
Filtrování a složené dotazy
Doposud jste pracovali pouze s jednoduchým dotazem, bez jakýchkoli specifických možností filtrování. Chcete-li vytvořit o něco robustnější dotazy, můžete filtrovat podle konkrétních hodnot pomocí whereField
klauzule:
docRef.whereField(“name”, isEqualTo: searchString)
Data dotazu si můžete objednat a omezit pomocí order(by: )
a limit(to: )
způsoby takto:
docRef.order(by: "name").limit(5)
V aplikaci FirebaseDo jste již využili limit
se základním dotazem. Ve výše uvedeném úryvku jste také využili další funkci, složené dotazy, kde jsou příkaz i limit zřetězeny dohromady. Můžete řetězit tolik dotazů, kolik chcete, jako v následujícím příkladu:
docRef .whereField(“name”, isEqualTo: searchString) .whereField(“done”, isEqualTo: false) .order(by: "name") .limit(5)