Parse Swift: Implementierung von Datenbankbeziehungen
16 min
beziehungen einführung beziehungen sind ein grundlegendes merkmal zur organisation von datenobjekten, die in einer datenbank gespeichert sind parseswift bietet die notwendigen werkzeuge und methoden, um beziehungen zwischen klassen in ihrer back4app datenbank herzustellen je nach anwendungsfall können wir die folgenden arten von beziehungen identifizieren 1 1 eine beziehung, die nur zwei datenobjekte verbindet 1\ n eine beziehung zwischen einem datenobjekt und \<font color="#2166ae">n\</font> datenobjekten n\ n eine beziehung zwischen \<font color="#2166ae">n\</font> datenobjekten zu \<font color="#2166ae">n\</font> datenobjekten wie wir unten sehen werden, ist die implementierung von 1 1 beziehungen relativ einfach bei 1\ n und n\ n beziehungen umfasst die implementierung das \<font color="#2166ae">parserelation\</font> objekt, das von parseswift sdk es gibt zusätzliche alternativen zur implementierung von 1\ n und n\ n beziehungen aufgrund von effizienz und leistung empfehlen wir jedoch, unserem ansatz zu folgen dieses tutorial verwendet eine grundlegende app, die in xcode 12 und ios 14 erstellt wurde zu jeder zeit können sie auf das vollständige projekt über unsere github repositories zugreifen ios beispiel repository https //github com/templates back4app/ios crud to do list ziel um zu verstehen, wie beziehungen in einer back4app datenbank implementiert sind voraussetzungen um dieses schnellstartprojekt abzuschließen, benötigen sie xcode eine app, die bei back4app erstellt wurde befolgen sie das tutorial für neue parse apps https //www back4app com/docs/get started/new parse app um zu lernen, wie man eine parse app bei back4app erstellt hinweis befolgen sie das tutorial zur installation des parse sdk (swift) https //www back4app com/docs/ios/parse swift sdk um ein xcode projekt zu erstellen, das mit back4app verbunden ist verstehen unserer bücher app die projektvorlage ist eine buch app, in der der benutzer die details eines buches eingibt, um es in einer back4app datenbank zu speichern auf dem startbildschirm der app finden sie das formular dafür mit dem \<font color="#2166ae">+\</font> button, der sich oben rechts in der navigationsleiste befindet, können wir so viele verlage , genres und autoren hinzufügen, wie nötig sobald der benutzer ein buch eingibt, kann er den \<font color="#2166ae">buch hinzufügen\</font> button verwenden, um das buch in seiner back4app datenbank zu speichern darüber hinaus ermöglicht der \<font color="#2166ae">bücher auflisten\</font> button, alle bücher anzuzeigen, die der benutzer hinzugefügt hat, und auch deren beziehung zu den verlagen und autoren zu sehen schnellreferenz der befehle, die wir verwenden werden wir verwenden die objekte \<font color="#2166ae">autor\</font> , \<font color="#2166ae">verleger\</font> , \<font color="#2166ae">isbn\</font> und \<font color="#2166ae">buch\</font> genre 1 import foundation 2 import parseswift 3 4 struct genre parseobject { 5 6 7 var name string? 8 9 10 } author 1 import foundation 2 import parseswift 3 4 struct author parseobject { 5 6 7 var name string? 8 9 10 } publisher 1 import foundation 2 import parseswift 3 4 struct publisher parseobject { 5 6 7 var name string? 8 9 10 } isbn 1 import foundation 2 import parseswift 3 4 struct isbn parseobject { 5 6 7 var name string? 8 9 10 } book 1 import foundation 2 import parseswift 3 4 struct book parseobject { 5 6 7 var title string? 8 var publishingyear int? 9 var genre genre? 10 var isbn isbn? 11 12 13 } bevor instanzen dieser objekte in einer back4app datenbank gespeichert werden, müssen alle ihre eigenschaften den \<font color="#2166ae">codable\</font> und \<font color="#2166ae">hashable\</font> protokollen entsprechen wir nutzen die folgenden methoden zur verwaltung dieser objekte in der back4app datenbank create //when creating a new instance of author we use 1 var newauthor author = author(name "john doe") 2 3 // saves newauthor on your back4app database synchronously and returns the new saved item it throws and error if something went wrong 4 let savedauthor = try? newauthor save() 5 6 // saves newauthor on your back4app database asynchronously, and passes a result\<author, parseerror> object to the completion block to handle the save process 7 newauthor save { result in 8 // handle the result to check wether the save process was successfull or not 9 } read //for reading any of the objects introduced above, we construct the corresponding queries for each of them for author we have 1 let authorquery = author query() // a query to fetch all author items on your back4app database 2 3 // fetches the items synchronously or throws an error if found 4 let fetchedauthors = try? query find() 5 6 // fetches the items asynchronously and calls a completion block passing a result object containing the result of the operation 7 query find { result in 8 // handle the result 9 } add relations //in 1 1 relations, it is sufficient to have the child object as a property of the parent object back4app automatically saves the child object when the parent object is saved for the remaining relations, we use the add( objects ) method via the relation property available in the parent parseobject adding a relation called authors on a book object would look like this 1 let somebook book 2 let authors \[author] 3 4 // adds the relation between somebook and authors under the name 'authors' 5 let booktoauthorsrelation = try? somebook relation? add("authors", objects authors) 6 7 // saves the relation synchronously 8 let updatedsomebook = try? booktoauthorsrelation save() 9 10 // saves the relation asynchronously 11 booktoauthorsrelation save { result in 12 // handle the result 13 } query relations //for 1 1 relations, it is enough to append the include() method in the query for instance, to query all the books together with their isbn relation, we use 1 var query = book query() include("isbn") 2 3 // fetches all books synchronously 4 let books = try? query find() 5 6 // fetches all books asynchronously 7 query find { result in 8 // handle the result 9 } remaining relations //for the remaining relations, we create a query by using the static method queryrelation( ,parent ) provided by the parseobject protocol querying the authors related to a book can be implemented in the following way 1 let somebook book 2 let authors \[author] 3 4 5 // we create a relation (identified by the name 'authors') betwee somebook and a set of authors 6 let booktoauthorsrelation = 7 guard let booktoauthorsrelation = try somebook relation? add("authors", objects authors) // book > author 8 else { 9 fatalerror("failed to add relation") 10 } 11 12 let savedrelation = try booktoauthorsrelation save() // saves the relation synchronously 13 14 booktoauthorsrelation save { result in // saves the relation asynchronously 15 // handle the result 16 } 1 lade die bücher app vorlage herunter das \<font color="#2166ae">xcode\</font> projekt hat die folgende struktur zu jeder zeit können sie auf das vollständige projekt über unsere github repositories zugreifen ios beispiel repository https //github com/templates back4app/ios crud to do list 2 zusätzlicher crud flow bevor wir fortfahren, ist es notwendig, einige crud funktionen zu implementieren, damit wir die \<font color="#2166ae">autor\</font> , \<font color="#2166ae">verlag\</font> und \<font color="#2166ae">genre\</font> objekte speichern können in der \<font color="#2166ae">maincontroller+parseswift swift\</font> datei, unter einer erweiterung für die \<font color="#2166ae">maincontroller\</font> klasse, haben wir die folgenden methoden implementiert 1 // maincontroller+parseswift swift file 2 extension maincontroller { 3 /// collects the data to save an instance of book on your back4app database 4 func savebook() { 5 view\ endediting(true) 6 7 // 1 first retrieve all the information for the book (title, isbn, etc) 8 guard let booktitle = booktitletextfield text else { 9 return presentalert(title "error", message "invalid book title") 10 } 11 12 guard let isbnvalue = isbntextfield text else { 13 return presentalert(title "error", message "invalid isbn value ") 14 } 15 16 let query = isbn query("value" == isbnvalue) 17 18 guard (try? query first()) == nil else { 19 return presentalert(title "error", message "the entered isbn already exists ") 20 } 21 22 guard let genreobjectid = genreoptionsview\ selectedoptionids first, 23 let genre = genres first(where { $0 objectid == genreobjectid}) 24 else { 25 return presentalert(title "error", message "invalid genre ") 26 } 27 28 guard let publishingyearstring = publishingyeartextfield text, let publishingyear = int(publishingyearstring) else { 29 return presentalert(title "error", message "invalid publishing year ") 30 } 31 32 let authors \[author] = self authoroptionsview\ selectedoptionids compactmap { \[weak self] objectid in 33 self? authors first(where { objectid == $0 objectid }) 34 } 35 36 let publishers \[publisher] = self publisheroptionsview\ selectedoptionids compactmap { \[weak self] objectid in 37 self? publishers first(where { objectid == $0 objectid }) 38 } 39 40 // since we are making multiple requests to back4app, it is better to use synchronous methods and dispatch them on the background queue 41 dispatchqueue global(qos background) async { 42 do { 43 let isbn = isbn(value isbnvalue) // 2 instantiate a new isbn object 44 45 let savedbook = try book( // 3 instantiate a new book object with the corresponding input fields 46 title booktitle, 47 publishingyear publishingyear, 48 genre genre, 49 isbn isbn 50 ) save() // 4 save the new book object 51 52 // here we will implement the relations 53 54 dispatchqueue main async { 55 self presentalert(title "success", message "book saved successfully ") 56 } 57 } catch { 58 dispatchqueue main async { 59 self presentalert(title "error", message "failed to save book \\((error as! parseerror) message)") 60 } 61 } 62 } 63 } 64 65 /// retrieves all the data saved under the genre class in your back4app database 66 func fetchgenres() { 67 let query = genre query() 68 69 query find { \[weak self] result in 70 switch result { 71 case success(let genres) 72 self? genres = genres // when setting self? genres, it triggers the corresponding ui update 73 case failure(let error) 74 self? presentalert(title "error", message error message) 75 } 76 } 77 } 78 79 /// presents a simple alert where the user can enter the name of a genre to save it on your back4app database 80 func handleaddgenre() { 81 // displays a form with a single input and executes the completion block when the user presses the submit button 82 presentform( 83 title "add genre", 84 description "enter a description for the genre", 85 placeholder nil 86 ) { \[weak self] name in 87 guard let name = name else { return } 88 let genre = genre(name name) 89 90 let query = genre query("name" == name) 91 92 guard ((try? query first()) == nil) else { 93 self? presentalert(title "error", message "this genre already exists ") 94 return 95 } 96 97 genre save { \[weak self] result in 98 switch result { 99 case success(let addedgenre) 100 self? presentalert(title "success", message "genre added!") 101 self? genres append(addedgenre) 102 case failure(let error) 103 self? presentalert(title "error", message "failed to save genre \\(error message)") 104 } 105 } 106 } 107 } 108 109 /// retrieves all the data saved under the publisher class in your back4app database 110 func fetchpublishers() { 111 let query = publisher query() 112 113 query find { \[weak self] result in 114 switch result { 115 case success(let publishers) 116 self? publishers = publishers 117 case failure(let error) 118 self? presentalert(title "error", message error message) 119 } 120 } 121 } 122 123 /// presents a simple alert where the user can enter the name of a publisher to save it on your back4app database 124 func handleaddpublisher() { 125 // displays a form with a single input and executes the completion block when the user presses the submit button 126 presentform( 127 title "add publisher", 128 description "enter the name of the publisher", 129 placeholder nil 130 ) { \[weak self] name in 131 guard let name = name else { return } 132 133 let query = publisher query("name" == name) 134 135 guard ((try? query first()) == nil) else { 136 self? presentalert(title "error", message "this publisher already exists ") 137 return 138 } 139 140 let publisher = publisher(name name) 141 142 publisher save { \[weak self] result in 143 switch result { 144 case success(let addedpublisher) 145 self? presentalert(title "success", message "publisher added!") 146 self? publishers append(addedpublisher) 147 case failure(let error) 148 self? presentalert(title "error", message "failed to save publisher \\(error message)") 149 } 150 } 151 } 152 } 153 154 /// retrieves all the data saved under the genre class in your back4app database 155 func fetchauthors() { 156 let query = author query() 157 158 query find { \[weak self] result in 159 switch result { 160 case success(let authors) 161 self? authors = authors 162 case failure(let error) 163 self? presentalert(title "error", message error message) 164 } 165 } 166 } 167 168 /// presents a simple alert where the user can enter the name of an author to save it on your back4app database 169 func handleaddauthor() { 170 // displays a form with a single input and executes the completion block when the user presses the submit button 171 presentform( 172 title "add author", 173 description "enter the name of the author", 174 placeholder nil 175 ) { \[weak self] name in 176 guard let name = name else { return } 177 178 let query = author query("name" == name) 179 180 guard ((try? query first()) == nil) else { 181 self? presentalert(title "error", message "this author already exists ") 182 return 183 } 184 185 let author = author(name name) 186 187 author save { \[weak self] result in 188 switch result { 189 case success(let addedauthor) 190 self? presentalert(title "success", message "author added!") 191 self? authors append(addedauthor) 192 case failure(let error) 193 self? presentalert(title "error", message "failed to save author \\(error message)") 194 } 195 } 196 } 197 } 198 }sw für weitere details zu diesem schritt können sie zum leitfaden für grundlegende operationen https //www back4app com/docs/ios/parse swift sdk/data objects/swift crud database operations 3 beziehungen einrichten bevor sie beginnen, beziehungen zu erstellen, werfen sie einen blick auf die schnelle referenz https //www back4app com/docs/ios/parse swift sdk/data objects/relationships#quick reference , um eine vorstellung von den objekten zu bekommen, die wir miteinander in beziehung setzen möchten in der folgenden abbildung zeigen wir, wie diese objekte miteinander verbunden sind wie zu sehen ist, werden die beziehungen hergestellt, indem das \<font color="#2166ae">buch\</font> objekt in die mitte gesetzt wird die pfeile zeigen, wie jedes objekt mit einem \<font color="#2166ae">buch\</font> objekt verbunden ist 4 beziehungen implementieren 1 1 fall das hinzufügen von 1 1 beziehungen kann einfach erreicht werden, indem eine eigenschaft im \<font color="#2166ae">buch\</font> objekt hinzugefügt wird, d h , 1 struct book parseobject { 2 3 4 var isbn isbn? // esablishes a 1 1 relation between book and isbn 5 6 7 } in diesem fall, \<font color="#2166ae">buch\</font> und \<font color="#2166ae">isbn\</font> teilen eine 1 1 beziehung, in der \<font color="#2166ae">buch\</font> als das elternteil und \<font color="#2166ae">isbn\</font> als das kind identifiziert wird intern, wenn back4app eine instanz von \<font color="#2166ae">buch\</font> , speichert es zuerst das \<font color="#2166ae">isbn\</font> objekt (unter dem \<font color="#2166ae">isbn\</font> klassennamen) nachdem dieser prozess abgeschlossen ist, fährt back4app mit dem objekt \<font color="#2166ae">buch\</font> das neue \<font color="#2166ae">buch\</font> objekt wird so gespeichert, dass seine \<font color="#2166ae">isbn\</font> eigenschaft durch ein \<font color="#2166ae">pointer\<isbn\>\</font> objekt dargestellt wird ein \<font color="#2166ae">pointer\<\>\</font> objekt ermöglicht es uns, eine eindeutige instanz des \<font color="#2166ae">isbn\</font> objekts zu speichern, das mit seinem entsprechenden elternteil verbunden ist 1\ n fall für 1\ n beziehungen ist der effizienteste weg, sie über ein \<font color="#2166ae">parserelation\<buch\>\</font> objekt zu implementieren parseswift bietet eine reihe von methoden, um diese arten von beziehungen für jedes objekt, das dem \<font color="#2166ae">parseobject\</font> protokoll entspricht, hinzuzufügen zum beispiel, wenn wir eine \<font color="#2166ae">1\ n\</font> beziehung zwischen \<font color="#2166ae">buch\</font> und \<font color="#2166ae">autor\</font> erstellen möchten, können wir verwenden 1 let somebook book 2 let authors \[author] 3 4 5 // we create a relation (identified by the name 'authors') between somebook and a set of authors 6 let booktoauthorsrelation = 7 guard let booktoauthorsrelation = try somebook relation? add("authors", objects authors) // book > author 8 else { 9 fatalerror("failed to add relation") 10 } 11 12 let savedrelation = try booktoauthorsrelation save() // saves the relation synchronously 13 14 booktoauthorsrelation save { result in // saves the relation asynchronously 15 // handle the result 16 } es ist einfach, diesen codeausschnitt für die anderen beziehungen, die wir für \<font color="#2166ae">buch\</font> anzupassen \ alles zusammenfassen nachdem wir die grundidee zur implementierung von beziehungen festgelegt haben, vervollständigen wir nun die \<font color="#2166ae">savebook()\</font> methode wir enumerieren die wichtigsten punkte, die während dieses prozesses zu beachten sind 1 extension maincontroller { 2 /// collects the data to save an instance of book on your back4app database 3 func savebook() { 4 5 // 1 first retrieve all the information for the book (booktitle, isbnvalue, etc) 6 7 8 // since we are making multiple requests to back4app, it is better to use synchronous methods and dispatch them on the background queue 9 dispatchqueue global(qos background) async { 10 do { 11 let isbn = isbn(value isbnvalue) // 2 instantiate a new isbn object 12 13 let savedbook = try book( // 3 instantiate a new book object with the corresponding input fields 14 title booktitle, 15 publishingyear publishingyear, 16 genre genre, 17 isbn isbn 18 ) save() // 4 save the new book object 19 20 // 5 add the corresponding relations for new book object 21 guard let booktoauthorsrelation = try savedbook relation? add("authors", objects authors), // book > author 22 let bootktopublishersrelation = try savedbook relation? add("publishers", objects publishers), // book > publisher 23 let genrerelation = try genre relation? add("books", objects \[savedbook]) // genre > book 24 else { 25 return dispatchqueue main async { 26 self presentalert(title "error", message "failed to add relations") 27 } 28 } 29 30 // 6 save the relations 31 = try booktoauthorsrelation save() 32 = try bootktopublishersrelation save() 33 = try genrerelation save() 34 35 dispatchqueue main async { 36 self presentalert(title "success", message "book saved successfully ") 37 } 38 } catch { 39 dispatchqueue main async { 40 self presentalert(title "error", message "failed to save book \\((error as! parseerror) message)") 41 } 42 } 43 } 44 } 45 46 47 } 5 abfragen von beziehungen für 1 1 beziehungen, gegeben das übergeordnete \<font color="#2166ae">buch\</font> und sein kind \<font color="#2166ae">isbn\</font> , fragen wir das entsprechende kind ab, indem wir es in die \<font color="#2166ae">buch\</font> abfrage einfügen 1 let query = book query() include("isbn") 2 3 let books = try query find() // retrieves synchronously all the books together with its isbn 4 5 query find { result in // retrieves asynchronously all the books together with its isbn 6 // handle the result 7 } 8 damit haben alle bücher aus der query auch die isbn eigenschaft korrekt mit dem zugehörigen isbn objekt gesetzt andererseits, um objekte mit einer 1\ n beziehung abzurufen, bietet das parseobject protokoll die statische methode queryrelation( ,parent ) indem wir den namen der beziehung (als ersten parameter) und den elternteil angeben, ermöglicht uns diese methode, die erforderliche abfrage zu erstellen zum beispiel, um alle author s abzurufen, die mit einem bestimmten book verbunden sind, können wir den folgenden code schnipsel verwenden 1 let book book // book from which we are trying to retrieve its related authors 2 3 do { 4 let authorsquery = try author queryrelations("authors", parent book) // 'authors' is the name of the relation it was saved with 5 6 authorsquery find { \[weak self] result in 7 switch result { 8 case success(let authors) 9 self? authors = authors 10 11 dispatchqueue main async { 12 // update the ui 13 } 14 case failure(let error) 15 dispatchqueue main async { 16 self? presentalert(title "error", message "failed to retrieve authors \\(error message)") 17 } 18 } 19 } 20 } 21 } catch { 22 if let error = error as? parseerror { 23 presentalert(title "error", message "failed to retrieve authors \\(error message)") 24 } else { 25 presentalert(title "error", message "failed to retrieve authors \\(error localizeddescription)") 26 } 27 } ähnlich können wir andere verwandte objekte abfragen, wie publisher in der bookdetailscontroller swift datei implementieren wir diese abfragen, um die beziehung eines buches zu autoren und verlegern anzuzeigen 1 // bookdetailscontroller swift file 2 class bookdetailscontroller uitableviewcontroller { 3 4 5 /// retrieves the book's details, i e , its relation with authors and publishers 6 private func fetchdetails() { 7 do { 8 // constructs the relations you want to query 9 let publishersquery = try publisher queryrelations("publishers", parent book) 10 let authorsquery = try author queryrelations("authors", parent book) 11 12 // obtains the publishers related to book and display them on the tableview, it presents an error if happened 13 publishersquery find { \[weak self] result in 14 switch result { 15 case success(let publishers) 16 self? publishers = publishers 17 18 // update the ui 19 dispatchqueue main async { 20 self? tableview\ reloadsections(indexset(\[section publisher rawvalue]), with none) 21 } 22 case failure(let error) 23 dispatchqueue main async { 24 self? presentalert(title "error", message "failed to retrieve publishers \\(error message)") 25 } 26 } 27 } 28 29 // obtains the authors related to book and display them on the tableview, it presents an error if happened 30 authorsquery find { \[weak self] result in 31 switch result { 32 case success(let authors) 33 self? authors = authors 34 35 // update the ui 36 dispatchqueue main async { 37 self? tableview\ reloadsections(indexset(\[section author rawvalue]), with none) 38 } 39 case failure(let error) 40 dispatchqueue main async { 41 self? presentalert(title "error", message "failed to retrieve authors \\(error message)") 42 } 43 } 44 } 45 } catch { // if there was an error during the creation of the queries, this block should catch it 46 if let error = error as? parseerror { 47 presentalert(title "error", message "failed to retrieve authors \\(error message)") 48 } else { 49 presentalert(title "error", message "failed to retrieve authors \\(error localizeddescription)") 50 } 51 } 52 } 53 54 55 } 6 führen sie die app aus! bevor sie die ausführen schaltfläche auf \<font color="#2166ae">xcode\</font> , vergessen sie nicht, ihre \<font color="#2166ae">back4app \</font> anwendung in der \<font color="#2166ae">appdelegate\</font> klasse zu konfigurieren! sie müssen ein paar genre ’s, publisher ’s und author ’s hinzufügen, bevor sie ein neues buch hinzufügen dann können sie beginnen, die informationen eines buches einzugeben, um es in ihrer back4app datenbank zu speichern sobald sie ein buch gespeichert haben, öffnen sie ihr back4app dashboard https //parse dashboard back4app com/apps und gehen sie zu ihrer anwendung, die mit dem xcode projekt verknüpft ist im datenbankbereich finden sie die klasse \<font color="#2166ae">book\</font> in der alle bücher gespeichert sind, die von der \<font color="#2166ae">ios\</font> app erstellt wurden zusätzlich können sie sehen, dass back4app automatisch die klasse \<font color="#2166ae">isbn\</font> erstellt hat, um sie mit dem entsprechenden \<font color="#2166ae">book\</font> objekt zu verknüpfen wenn sie zur \<font color="#2166ae">book\</font> klasse zurückkehren, können sie die datentypen für jede art von beziehung identifizieren im fall von \<font color="#2166ae">isbn\</font> und \<font color="#2166ae">genre\</font> , ist der datentyp ein \<font color="#2166ae">pointer\<\>\</font> andererseits ist der datentyp für beziehungen wie \<font color="#2166ae">author\</font> und \<font color="#2166ae">publisher\</font> , \<font color="#2166ae">relation\<\>\</font> dies ist ein wichtiger unterschied, den man beim erstellen von beziehungen im hinterkopf behalten sollte