iOS
...
Data Objects
스위프트 데이터 유형 파싱 및 저장 방법
15 분
스위프트에서 데이터 유형 파싱 소개 back4app 데이터베이스에 데이터를 저장할 때, 각 엔티티는 키 값 쌍 형식으로 저장됩니다 값 필드의 데이터 유형은 기본적인 것들(예 string , int , double , float , 및 bool )에서 더 복잡한 구조로 확장됩니다 back4app 데이터베이스에 데이터를 저장하기 위한 주요 요구 사항은 엔티티가 parseswift 프로토콜을 준수해야 한다는 것입니다 이 프로토콜은 엔티티의 인스턴스를 저장, 업데이트 및 삭제하는 메서드 집합을 제공합니다 이 가이드에서는 back4app 데이터베이스에 저장할 엔티티를 생성하고 설정하는 방법을 배웁니다 프로젝트 예제에서 우리가 저장하는 엔티티는 레시피에 대한 정보를 포함합니다 이 튜토리얼은 xcode 12에서 생성된 기본 앱과 ios 14 을 사용합니다 언제든지 github 리포지토리를 통해 전체 프로젝트에 접근할 수 있습니다 ios 예제 리포지토리 목표 객체가 back4app 데이터베이스에서 어떻게 파싱되고 저장되는지 이해하기 위해 전제 조건 이 빠른 시작을 완료하려면 다음이 필요합니다 xcode back4app에서 생성된 앱 다음 튜토리얼을 따르세요 new parse app 튜토리얼 back4app에서 parse 앱을 만드는 방법을 배우세요 참고 다음 튜토리얼을 따르세요 parse sdk (swift) 설치 튜토리얼 back4app에 연결된 xcode 프로젝트를 생성하세요 우리 레시피 앱 이해하기 앱 기능은 레시피에 대한 정보를 입력할 수 있는 양식을 기반으로 합니다 정보에 따라 데이터 유형이 달라질 수 있습니다 우리의 예에서 레시피는 다음과 같은 기능을 가지고 있습니다 필드 데이터 유형 설명 이름 문자열 레시피 이름 인분 정수 서빙 수 사용 가능 부울 레시피가 사용 가능한지 여부를 결정합니다 카테고리 카테고리 레시피를 아침, 점심, 저녁의 세 가지 범주로 분류하는 사용자 정의 열거형 재료 \[재료] 사용자 정의 재료 구조체에 포함된 재료 세트 측면 옵션 \[문자열] 레시피에 포함된 추가 옵션의 이름 영양 정보 \[문자열 문자열] 레시피의 영양 성분에 대한 정보를 포함하는 사전 출시 날짜 날짜 레시피가 제공된 날짜 또한, 객체 간의 관계와 같은 데이터베이스 기능을 구현하는 데 사용되는 더 많은 데이터 유형이 있습니다 이러한 데이터 유형은 이 튜토리얼에서 다루지 않습니다 우리가 사용할 명령어의 빠른 참조 예를 들어 recipe라는 객체를 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 데이터베이스에서 레시피 객체를 생성하고 업데이트하기 위한 양식을 배치할 것입니다 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에 필요한 모든 속성을 추가했습니다 재료 데이터 유형은 재료의 양과 설명을 담고 있는 구조체입니다 앞서 언급했듯이, 이 데이터 유형은 recipe의 속성의 일부가 되기 위해 codable 및 hashable 프로토콜을 준수해야 합니다 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에서는 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로 정수 데이터 유형을 가지므로 숫자 값 유형으로 변환됩니다 releasedate 속성은 swift 에서 date 유형 값으로 저장됩니다 결론적으로, back4app 데이터베이스에서 데이터를 검색할 때 이러한 모든 필드를 수동으로 디코딩할 필요가 없습니다 parseswift sdk가 자동으로 디코딩합니다 즉, 데이터를 검색하기 위해 쿼리(이 경우 query\<recipe>)를 생성할 때 query find() 메서드는 모든 데이터 유형과 json 객체를 파싱하여 recipe 배열을 반환하므로 추가적인 파싱 절차를 구현할 필요가 없습니다