iOS
...
Data Objects
ParseSwiftプロトコル準拠のデータ型を操作する方法
13 分
swiftでデータ型を解析する はじめに back4appデータベースにデータを保存する際、各エンティティはキーと値のペア形式で保存されます。値フィールドのデータ型は、基本的なもの(例えば、 string , int , double , float , および bool ) から、より複雑な構造に至ります。back4appデータベースにデータを保存するための主な要件は、エンティティが parseswift プロトコルに準拠している必要があることです。このプロトコルは、エンティティのインスタンスを保存、更新、削除するための一連のメソッドを提供します。 このガイドでは、エンティティを作成し、back4appデータベースに保存する方法を学びます。プロジェクトの例では、保存するエンティティはレシピに関する情報を含んでいます。 このチュートリアルでは、xcode 12で作成された基本的なアプリを使用し、 ios 14 を使用します。 いつでも、私たちのgithubリポジトリを通じて完全なプロジェクトにアクセスできます。 iosの例リポジトリ 目標 オブジェクトがどのように解析され、back4appデータベースに保存されるかを理解するため。 前提条件 このクイックスタートを完了するには、次のものが必要です: xcode。 back4appで作成されたアプリ。 次の new parse app チュートリアル をフォローして、back4appでparseアプリを作成する方法を学んでください。 注意: 次の install parse sdk (swift) チュートリアル をフォローして、back4appに接続されたxcodeプロジェクトを作成してください。 私たちのレシピアプリの理解 アプリの機能は、レシピに関する情報を入力できるフォームに基づいています。情報に応じて、データの種類は異なる場合があります。私たちの例では、レシピには以下の特徴があります フィールド データ型 説明 名前 文字列 レシピの名前 サービング 整数 サービングの数 利用可能 ブール レシピが利用可能かどうかを判断します カテゴリ カテゴリ レシピを朝食、昼食、夕食の3つのカテゴリに分類するカスタム列挙型 材料 \[材料] カスタム成分 構造体に囲まれた成分のセット サイドオプション \[文字列] レシピに付属する追加オプションの名前 栄養情報 \[文字列 文字列] レシピの栄養成分に関する情報を含む辞書 リリース日 日付 レシピが利用可能だった日付 さらに、オブジェクト間の関係のようなデータベース機能を実装するために使用されるデータ型がいくつかあります。これらのデータ型はこのチュートリアルではカバーされていません。 使用するコマンドのクイックリファレンス オブジェクト、例えばレシピを考えた場合、back4appデータベースに保存したい場合は、まずこのオブジェクトをparseswiftプロトコルに準拠させる必要があります(parseswift sdkを介して利用可能)。 1 import foundation 2 import parseswift 3 4 struct recipe parseobject { 5 /// enumeration for the recipe category 6 enum category int, caseiterable, codable { 7 case breakfast = 0, lunch = 1, dinner = 2 8 9 var title string { 10 switch self { 11 case breakfast return "breakfast" 12 case lunch return "lunch" 13 case dinner return "dinner" 14 } 15 } 16 } 17 18 19 20 /// a string type property 21 var name string? 22 23 /// an integer type property 24 var servings int? 25 26 /// a double (or float ) type property 27 var price double? 28 29 /// a boolean type property 30 var isavailable bool? 31 32 /// an enumeration type property 33 var category category? 34 35 /// an array of structs 36 var ingredients \[ingredient] 37 38 /// an array of strings 39 var sideoptions \[string] 40 41 /// a dictionary property 42 var nutritionalinfo \[string string] 43 44 /// a date type property 45 var releasedate date? 46 } このオブジェクトのインスタンスをback4appデータベースに保存する前に、すべてのプロパティはcodableおよびhashableプロトコルに準拠している必要があります。 これらのオブジェクトをback4appデータベースで管理するために、以下のメソッドを使用します create //the procedure for reading and updating a recipe object is similar since they rely on the save() method how a recipe is instantiated determines if we are creating or updating the object on the back4app database when creating a new instance we use 1 var newrecipe recipe 2 3 // setup newrecipe's properties 4 newrecipe name = "my recipe's name" 5 newrecipe servings = 4 6 newrecipe price = 3 99 7 newrecipe isavailable = false 8 newrecipe category = breakfast 9 newrecipe sideoptions = \["juice"] 10 newrecipe releasedate = date() 11 12 13 // saves newrecipe on your back4app database synchronously and returns the new saved item it throws and error if something went wrong 14 let savedrecipe = try? savedrecipe save() 15 16 // saves savedrecipe on your back4app database asynchronously, and passes a result\<todolistitem, parseerror> object to the completion block to handle the save proccess 17 savedrecipe save { result in 18 // handle the result to check the save was successfull or not 19 } update //and to update an existing instance, we have to provide the objectid value which identifies the the object on the back4app database a satandard update can be implemented in the following way 1 let recipetoupdate = recipe(objectid "object id") 2 3 // update the properties you need 4 recipetoupdate name = "my updated recipe's name" 5 recipetoupdate servings = 5 6 recipetoupdate price = 5 99 7 recipetoupdate isavailable = true 8 recipetoupdate category = lunch 9 recipetoupdate sideoptions = \["juice", "coffee"] 10 recipetoupdate releasedate = date() addingtimeinterval(3600 24) 11 12 13 // save changes synchronousty 14 try? recipetoupdate save() 15 16 // or save changes asynchronously 17 recipetoupdate save { result in 18 // handle the result 19 } read //for reading objects stored on your back4app database, recipe now provides the query() static method which returns a query\<recipe> this query object can be constructed using one or more queryconstraint objects in the following way 1 let query = recipe query() // a query to fetch all recipe items on your back4app database 2 let query = recipe query("name" == "omelette") // a query to fetch all recipe items with name "omelette" on your back4app database 3 let query = recipe query(\["name" == "omelette", "price" = 9 99]) // a query to fetch all recipe items with name = "omelette" and price = 9 99 4 5 // fetches the items synchronously or throws an error if found 6 let fetchedrecipes = try? query find() 7 8 // fetches the items asynchronously and calls a completion block passing a result object containing the result of the operation 9 query find { result in 10 // handle the result 11 } delete //any deletion process is performed by calling the method delete() on the object to be deleted 1 var recipetodelete recipe 2 3 // delete recipetodelete synchronously 4 try? recipetodelete delete() 5 6 // delete recipetodelete asynchronously 7 recipetodelete delete { result in 8 // handle the result 9 } 1 レシピアプリテンプレートを作成する 新しいxcodeプロジェクトを作成することから始めます。このチュートリアルでは、プロジェクトは次のようになります。 いつでも、私たちのgithubリポジトリを通じてプロジェクト全体にアクセスできます。 iosのサンプルリポジトリ xcodeに移動し、scenedelegate swiftファイルを見つけます。アプリの上部にナビゲーションバーを追加するために、次のようにuinavigationcontrollerをルートビューコントローラーとして設定します。 1 class scenedelegate uiresponder, uiwindowscenedelegate { 2 3 var window uiwindow? 4 5 func scene( scene uiscene, willconnectto session uiscenesession, options connectionoptions uiscene connectionoptions) { 6 guard let scene = (scene as? uiwindowscene) else { return } 7 8 window = init(windowscene scene) 9 window? rootviewcontroller = uinavigationcontroller(rootviewcontroller recipescontroller()) 10 window? makekeyandvisible() 11 12 // additional logic 13 } 14 15 16 } ナビゲーションコントローラーのルートビューコントローラークラス(recipescontroller)は、back4appデータベースでレシピオブジェクトを作成および更新するためのフォームをレイアウトするuiviewcontrollerのサブクラスです。 2 レシピオブジェクトのセットアップ back4appデータベースに保存したいオブジェクトは、parseobjectプロトコルに準拠する必要があります。私たちのレシピアプリでは、このオブジェクトはrecipeです。したがって、最初にこのオブジェクトを作成する必要があります。新しいファイルrecipe swiftを作成し、次の内容を追加します。 1 import foundation 2 import parseswift 3 4 struct recipe parseobject { 5 /// enumeration for the recipe category 6 enum category int, caseiterable, codable { 7 case breakfast = 0, lunch = 1, dinner = 2 8 9 var title string { 10 switch self { 11 case breakfast return "breakfast" 12 case lunch return "lunch" 13 case dinner return "dinner" 14 } 15 } 16 } 17 18 // required properties from parseobject protocol 19 var objectid string? 20 var createdat date? 21 var updatedat date? 22 var acl parseacl? 23 24 /// a string type property 25 var name string? 26 27 /// an integer type property 28 var servings int? 29 30 /// a double (or float ) type property 31 var price double? 32 33 /// a boolean type property 34 var isavailable bool? 35 36 /// an enumeration type property 37 var category category? 38 39 /// an array of structs 40 var ingredients \[ingredient] 41 42 /// an array of strings 43 var sideoptions \[string] 44 45 /// a dictionary property 46 var nutritionalinfo \[string string] 47 48 /// a date type property 49 var releasedate date? 50 51 /// maps the nutritionalinfo property into an array of tuples 52 func nutritionalinfoarray() > \[(name string, value string)] { 53 return nutritionalinfo map { ($0 key, $0 value) } 54 } 55 } ここでは、レシピの機能テーブルに従って、recipeに必要なすべてのプロパティをすでに追加しました。 ingredientデータ型は、材料の数量と説明を保持する構造体です。前述のように、このデータ型はrecipeのプロパティの一部となるためにcodableおよびhashableプロトコルに準拠する必要があります。 1 import foundation 2 3 struct ingredient hashable, codable { 4 var quantity float 5 var description string 6 } さらに、recipeのプロパティcategoryは、対応するプロトコルに準拠したデータ型として列挙型(category)を持っています。 1 struct recipe parseobject { 2 /// enumeration for the recipe category 3 enum category int, caseiterable, codable { 4 case breakfast = 0, lunch = 1, dinner = 2 5 6 7 } 8 9 } 3 recipescontrollerの設定 recipescontrollerでは、navigationbarとすべてのrecipeプロパティをキャプチャするために使用されるフォームの必要な設定を実装する必要があります。このチュートリアルでは、フォームのレイアウトを実装する方法はカバーしていません。そのため、parseswift sdkを使用してデータ型を管理することに関連するロジックに焦点を当てます。以下に、ユーザーインターフェースとback4appデータベースからのデータとの接続を実装する方法を理解するためのrecipescontrollerの重要なポイントを強調します。 1 class recipescontroller uiviewcontroller { 2 enum previousnext int { case previous = 0, next = 1 } 3 4 5 6 var recipes \[recipe] = \[] // 1 an array of recipes fetched from your back4app database 7 8 // section header labels 9 private let recipelabel uilabel = titlelabel(title "recipe overview") 10 private let ingredientslabel uilabel = titlelabel(title "ingredients") 11 private let nutritionalinfolabel uilabel = titlelabel(title "nutritional information") 12 13 // 2 a custom view containing input fields to enter the recipe's information (except nutritional info and ingredients) 14 let recipeoverviewview recipeinfoview 15 16 // 3 a stack view containig the fields to enter the recipe's ingredients 17 let ingredientsstackview uistackview 18 19 // 4 a stack view containig the fields to enter the nutritional information 20 let nutritionalinfostackview uistackview 21 22 // 5 buttons to handle the crud logic for the recipe object currently displayed 23 private var savebutton uibutton = uibutton(title "save") 24 private var updatebutton uibutton = uibutton(title "update") 25 private var reloadbutton uibutton = uibutton(title "reload") 26 27 var currentrecipeindex int? // 6 an integer containing the index of the current recipe presenten from the recipes property 28 29 override func viewdidload() { 30 super viewdidload() 31 setupnavigationbar() 32 setupviews() 33 } 34 35 override func viewdidappear( animated bool) { 36 super viewdidappear(animated) 37 handlereloadrecipes() 38 } 39 40 private func setupnavigationbar() { 41 navigationcontroller? navigationbar bartintcolor = primary 42 navigationcontroller? navigationbar titletextattributes = \[ foregroundcolor uicolor white] 43 navigationcontroller? navigationbar istranslucent = false 44 navigationcontroller? navigationbar barstyle = black 45 navigationitem title = "parse data types" uppercased() 46 } 47 48 private func setupviews() { 49 // see the project example for more details 50 51 savebutton addtarget(self, action #selector(handlesaverecipe), for touchupinside) 52 updatebutton addtarget(self, action #selector(handleupdaterecipe), for touchupinside) 53 reloadbutton addtarget(self, action #selector(handlereloadrecipes), for touchupinside) 54 } 55 56 57 } 3 ユーザー入力の処理とレシピオブジェクトの解析 別のファイル(recipescontroller+parseswiftlogic swiftと呼ばれる)で、拡張を使用して、入力データを処理するためのhandlesaverecipe()、handleupdaterecipe()およびhandleupdaterecipe()メソッドを実装します。 1 import uikit 2 import parseswift 3 4 extension recipescontroller { 5 /// retrieves all the recipes stored on your back4app database 6 @objc func handlereloadrecipes() { 7 view\ endediting(true) 8 let query = recipe query() 9 query find { \[weak self] result in // retrieves all the recipes stored on your back4app database and refreshes the ui acordingly 10 guard let self = self else { return } 11 switch result { 12 case success(let recipes) 13 self recipes = recipes 14 self currentrecipeindex = recipes isempty ? nil 0 15 self setuprecipenavigation() 16 17 dispatchqueue main async { self presentcurrentrecipe() } 18 case failure(let error) 19 dispatchqueue main async { self showalert(title "error", message error message) } 20 } 21 } 22 } 23 24 /// called when the user wants to update the information of the currently displayed recipe 25 @objc func handleupdaterecipe() { 26 view\ endediting(true) 27 guard let recipe = preparerecipemetadata(), recipe objectid != nil else { // prepares the recipe object for updating 28 return showalert(title "error", message "recipe not found ") 29 } 30 31 recipe save { \[weak self] result in 32 switch result { 33 case success(let newrecipe) 34 self? recipes append(newrecipe) 35 self? showalert(title "success", message "recipe saved on your back4app database! (objectid \\(newrecipe id)") 36 case failure(let error) 37 self? showalert(title "error", message "failedto save recipe \\(error message)") 38 } 39 } 40 } 41 42 /// saves the currently displayed recipe on your back4app database 43 @objc func handlesaverecipe() { 44 view\ endediting(true) 45 guard var recipe = preparerecipemetadata() else { // prepares the recipe object for storing 46 return showalert(title "error", message "failed to retrieve all the recipe fields ") 47 } 48 49 recipe objectid = nil // when saving a recipe object, we ensure it will be a new instance of it 50 recipe save { \[weak self] result in 51 switch result { 52 case success(let newrecipe) 53 if let index = self? currentrecipeindex { self? recipes\[index] = newrecipe } 54 self? showalert(title "success", message "recipe saved on your back4app database! (objectid \\(newrecipe id))") 55 case failure(let error) 56 self? showalert(title "error", message "failed to save recipe \\(error message)") 57 } 58 } 59 } 60 61 /// when called it refreshes the ui according to the content of recipes and currentrecipeindex properties 62 private func presentcurrentrecipe() { 63 64 } 65 66 /// adds the 'next recipe' and 'previous recipe' button on the navigation bar these are used to iterate over all the recipes retreived from your back4app database 67 private func setuprecipenavigation() { 68 69 } 70 71 /// reads the information the user entered via the form and returns it as a recipe object 72 private func preparerecipemetadata() > recipe? { 73 let ingredientscount = ingredientsstackview\ arrangedsubviews count 74 let nutritionalinfocount = nutritionalinfostackview\ arrangedsubviews count 75 76 let ingredients \[ingredient] = (0 \<ingredientscount) compactmap { row in 77 guard let textfields = ingredientsstackview\ arrangedsubviews\[row] as? doubletextfield, 78 let quantitystring = textfields primarytext, 79 let quantity = float(quantitystring), 80 let description = textfields secondarytext 81 else { 82 return nil 83 } 84 return ingredient(quantity quantity, description description) 85 } 86 87 var nutritionalinfo \[string string] = \[ ] 88 89 (0 \<nutritionalinfocount) foreach { row in 90 guard let textfields = nutritionalinfostackview\ arrangedsubviews\[row] as? doubletextfield, 91 let content = textfields primarytext, !content isempty, 92 let value = textfields secondarytext, !value isempty 93 else { 94 return 95 } 96 nutritionalinfo\[content] = value 97 } 98 99 let recipeinfo = recipeoverviewview\ parseinputtorecipe() // reads all the remaining fields from the form (name, category, price, serving, etc) and returns them as a tuple 100 101 // we collect all the information the user entered and create an instance of recipe 102 // the recipeinfo objectid will be nil if the currently displayed information does not correspond to a recipe already saved on your back4app database 103 let newrecipe recipe = recipe( 104 objectid recipeinfo objectid, 105 name recipeinfo name, 106 servings recipeinfo servings, 107 price recipeinfo price, 108 isavailable recipeinfo isavailable, 109 category recipeinfo category, 110 ingredients ingredients, 111 sideoptions recipeinfo sideoptions, 112 nutritionalinfo nutritionalinfo, 113 releasedate recipeinfo releasedate 114 ) 115 116 return newrecipe 117 } 118 119 /// called when the user presses the 'previous recipe' or 'next recipe' button 120 @objc private func handleswitchrecipe(button uibarbuttonitem) { 121 122 } 123 } 4 アプリを実行する! xcodeの実行ボタンを押す前に、appdelegateクラスでback4appアプリケーションを設定するのを忘れないでください! プロジェクトを初めて実行すると、シミュレーターにこのようなものが表示されるはずです(すべてのフィールドが空の状態で) 今、レシピを入力して、それをback4appデータベースに保存することができます。一つのレシピを保存したら、 back4appダッシュボード https //parse dashboard back4app com/apps に移動し、アプリケーションに行きます。データベースセクションでは、iosアプリによって作成されたすべてのレシピが含まれるrecipeクラスを見つけることができます。 特に、ingredient、recipe categoryや辞書のような非基本データ型がどのように保存されるかに注目する価値があります。recipeクラスの下に保存されたデータをナビゲートすると、 nutritionalinformation辞書はjsonオブジェクトとして保存されます。 \[ingredients]配列はjsonオブジェクトの配列として保存されます。 列挙型recipe categoryは、rawvalueとして整数データ型を持つため、number値型に変換されます。 releasedateプロパティは、 swift でのdate型値として、date型値としても保存されます。 結論として、back4appデータベースからデータを取得する際、これらのフィールドを手動でデコードする必要はありません。parseswift sdkは自動的にデコードします。つまり、データを取得するためのクエリ(この場合はquery\<recipe>)を作成すると、query find()メソッドはすべてのデータ型とjsonオブジェクトを解析してrecipe配列を返します。追加の解析手順を実装する必要はありません。