iOS
...
Data Objects
在ParseSwift SDK中处理iOS数据类型
13 分
在swift中解析数据类型 介绍 在back4app数据库中保存数据时,每个实体以键值对格式存储。值字段的数据类型从基本类型(如 string , int , double , float , 和 bool ) 到更复杂的结构。存储数据在back4app数据库的主要要求是实体必须符合 parseswift 协议。该协议提供了一组方法来存储、更新和删除任何实体的实例。 在本指南中,您将学习如何创建和设置一个实体以将其保存到您的back4app数据库。在项目示例中,我们存储的实体包含有关食谱的信息。 本教程使用在xcode 12中创建的基本应用程序和 ios 14 您可以随时通过我们的github仓库访问完整的项目。 ios示例仓库 目标 了解对象是如何在 back4app 数据库中解析和存储的。 先决条件 要完成此快速入门,您需要: xcode。 在 back4app 创建的应用程序。 请遵循 新 parse 应用教程 以了解如何在 back4app 上创建 parse 应用。 注意: 请遵循 安装 parse sdk (swift) 教程 以创建一个连接到 back4app 的 xcode 项目。 了解我们的食谱应用 该应用的功能基于一个表单,用户可以在其中输入有关食谱的信息。根据信息的不同,数据类型可能会有所不同。在我们的示例中,食谱具有以下特点: 字段 数据类型 描述 名称 字符串 食谱名称 份数 整数 份数 可用 布尔 确定食谱是否可用 类别 类别 一个自定义枚举,将食谱分类为三类:早餐、午餐和晚餐 成分 \[成分] 封装在自定义成分 结构中的成分集合 侧边选项 \[字符串] 食谱附带的额外选项名称 营养信息 \[字符串 字符串] 一个包含食谱营养成分信息的字典 发布日期 日期 显示食谱可用日期 此外,还有更多的数据类型用于实现数据库功能,例如对象之间的关系。这些数据类型在本教程中未涵盖。 我们将使用的命令快速参考 给定一个对象,比如食谱,如果你想将其保存在 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)是uiviewcontroller的子类,我们将在其中布局一个表单,以在back4app数据库上创建和更新recipe对象。 2 设置recipe对象 您想要保存到back4app数据库的对象必须符合parseobject协议。在我们的recipes应用中,这个对象是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所需的所有属性。 成分数据类型是一个结构体,包含成分的数量和描述。如前所述,该数据类型应符合codable和hashable协议,以成为recipe的属性的一部分。 1 import foundation 2 3 struct ingredient hashable, codable { 4 var quantity float 5 var description string 6 } 此外,recipe中的属性类别具有一个枚举(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中,我们应该实现导航栏和用于捕获所有recipe属性的表单所需的所有配置。本教程不涵盖如何实现表单的布局。然后,我们专注于使用parseswift sdk管理数据类型相关的逻辑。下面我们强调recipescontroller中的关键点,这些关键点使我们能够理解如何实现用户界面与来自back4app数据库的数据之间的连接。 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 ,并转到您的应用程序,在数据库部分,您将找到类 recipe,其中包含 ios 应用程序创建的所有食谱。 特别值得注意的是,像 ingredient、recipe category 或字典这样的非基本数据类型是如何存储的。如果您浏览 recipe 类下保存的数据,您会发现 营养信息字典作为 json 对象存储。 \[ingredients] 数组作为 json 对象数组存储。 枚举 recipe category,由于它的原始值是整数数据类型,因此被转换为数字值类型。 releasedate 属性,作为 swift 中的日期类型值,也作为日期类型值存储。 最后,当从您的 back4app 数据库中检索数据时,您不需要手动解码所有这些字段,parseswift sdk 会自动解码它们。这意味着,在创建查询(以 query\<recipe> 为例)以检索数据时,query find() 方法将解析所有数据类型和 json 对象,以返回一个 recipe 数组,无需实现额外的解析过程。