-
iOS - memo 앱 만들기 #10 DB 구현 1iOS 2020. 12. 15. 22:49
DB 구현 1
iOS에서 database를 구현할때는 Core Data를 사용한다.
우리가 프로젝트를 만들때 처음에 Core Data를 체크했기 때문에 기본적인 파일들이 프로젝트에 추가되어 있다.
[KxMemo.xcdatamodeld]
모델 파일은 데이터를 저장할 방식을 표현하는 설계도
여기에서 메모가 어떤 형식으로 저장되는지 설계도를 만들어야 한다.
하단에 add Entity 버튼을 클릭하면 새로운 Entity가 생긴다.
더블클릭해서 이름을 Memo라고 바꾸자
+ 버튼을 누르면 Attribute가 추가된다.
아래 화면과 같이 두개의 Attribute를 추가하자
이제 메모와 날짜는 Memo라는 Entity로 묶여서 DB에 저장된다.
모델 파일은 DB의 설계도이고 Entity는 table의 설계도라고 비유할 수 있다.
이제 우측의 Data Model Inspector를 보자
Codegen 항목을 보면 Class Deginition이라고 되어있는데 Entity는 Class 형태로 다뤄야 한다.
그리고 여기에 필요한 Class는 자동으로 생성된다. 생성되는 클래스의 이름은 Class 섹션의 name에 적혀있는 것과 같다.
여기까지 해서 프로젝트를 빌드하면 failed가 발생한다.
왜냐? 위의 Class 섹션의 name과 우리가 만들었던 Memo 클래스가 충돌하기 때문에!!
[Model.swift]
//class Memo{ // var content: String // var insertDate: Date // // init(content: String){ // self.content = content // self.insertDate = Date() // } // // static var dummyMemoList = [ // Memo(content: "Lorem Ipsum"), // Memo(content: "Subscribe + 🧡 = ❤️") // ] //} // 이 클래스와 모델 파일의 Entity 클래스와 충돌이 나서 주석 처리를 해도 // 이 클래스를 가져다 쓰는 코드가 많이 때문에 여전히 충돌 나는중 // 천천히 바꿔보자
이제 Core Data를 통해서 데이터를 처리하는 코드를 작성해야 한다.
[AppDelegate.swift]
// MARK: - Core Data Saving support // MARK: - Core Data stack lazy var persistentContainer: NSPersistentContainer = { /* The persistent container for the application. This implementation creates and returns a container, having loaded the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail. */ let container = NSPersistentContainer(name: "KxMemo") container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { // Replace this implementation with code to handle the error appropriately. // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. /* Typical reasons for an error here include: * The parent directory does not exist, cannot be created, or disallows writing. * The persistent store is not accessible, due to permissions or data protection when the device is locked. * The device is out of space. * The store could not be migrated to the current model version. Check the error message to determine what the actual problem was. */ fatalError("Unresolved error \(error), \(error.userInfo)") } }) return container }() func saveContext () { let context = persistentContainer.viewContext if context.hasChanges { do { try context.save() } catch { // Replace this implementation with code to handle the error appropriately. // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. let nserror = error as NSError fatalError("Unresolved error \(nserror), \(nserror.userInfo)") } } }
AppDelegate에 Core Data 관련 코드가 정의 되어 있는 것을 볼 수 있다.
사실 여기에서 있는 그대로 사용해도 문제는 없지만 이런 코드를 사용해서 구현하다보면 코드가 불필요하게 복잡해진다.
Core Data와 관련된 두개의 메소드에서 주석을 제거하고 잘라내기 한 후
새로 Data 폴더를 만들고 DataManager.swift 파일을 새로 생성한다.
[DataManager.swift]
import Foundation import CoreData // DataManager class는 singleton으로 구현 class DataManager{ // 밑에 두개를 해주면 앱 전체에서 하나의 인스턴스를 공유할 수 있다. // 1. 공유인스턴스를 저장할 타입 프로퍼티 추가 static let shared = DataManager() // 2. 기본생성자 private init(){ } // MARK: - Core Data stack lazy var persistentContainer: NSPersistentContainer = { let container = NSPersistentContainer(name: "KxMemo") container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { fatalError("Unresolved error \(error), \(error.userInfo)") } }) return container }() // MARK: - Core Data Saving support func saveContext () { let context = persistentContainer.viewContext if context.hasChanges { do { try context.save() } catch { // Replace this implementation with code to handle the error appropriately. // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. let nserror = error as NSError fatalError("Unresolved error \(nserror), \(nserror.userInfo)") } } } }
그리고 위쪽과 같은 모양으로 만들어준다.
그리고 컨텍스트 변수 생성
// Cora Data가 하는 거의 대부분의 작업은 컨텍스트 객체가 담당 // 우리가 직접 컨텍스트를 만들 수도 있지만 기본적으로 제공되는 컨텍스트를 생성 var mainContext: NSManagedObjectContext{ return persistentContainer.viewContext }
여기까지 하면 SceneDelegate.swift에서 에러가 나는 것을 확인 할 수 있는데
우리가 방금 새로 폴더를 만들고 스위프트 파일을 만든후 붙여 넣은 Core Data 관련 메소드 두개가 여전히 AppDelegate에 그대로
있었다면 위의 코드처럼 접근해야 한다.
하지만 우리는 이미 Sington으로 구현을 완료했기 때문에 타입캐스팅이나 옵셔널체인 같은걸 할필요가 없다.
밑에 코드처럼 바꿔준다.
func sceneDidEnterBackground(_ scene: UIScene) { // Called as the scene transitions from the foreground to the background. // Use this method to save data, release shared resources, and store enough scene-specific state information // to restore the scene back to its current state. // Save changes in the application's managed object context when the application transitions to the background. // (UIApplication.shared.delegate as? AppDelegate)?.saveContext() DataManager.shared.saveContext() }
그리고 Core Data가 하는 거의 대부분의 작업은 context가 처리하는데 이걸 직접 만들어 줄 수도 있지만
우리는 기존에 있는 걸 가져다가 쓰겠다.
또한 DB에서 데이터를 읽어 오는 것을 ios에서는 fetch라고 표현하는데
먼저 fetch request 를 만들고, Core data는 데이터를 정렬해서 주지 않기 때문에 우리가 직접 정렬하고
fetch 메소드를 do catch문을 이용해서 실행하고 그 값을 memoList라는 배열 변수에 저장한다.
// Cora Data가 하는 거의 대부분의 작업은 컨텍스트 객체가 담당 // 우리가 직접 컨텍스트를 만들 수도 있지만 기본적으로 제공되는 컨텍스트를 생성 var mainContext: NSManagedObjectContext{ return persistentContainer.viewContext } var memoList = [Memo]() // 데이터를 데이터베이스에 읽어오는 것을 표현하는 다양한 표현이 있는데 iOS에서는 이것을 패치(fetch)라고 한다. // 데이터베이스에서 데이터를 읽어올때는 먼저 fetch request를 만들어어야 한다. // 밑의 메소드는 DB에 저장되어 잇는 메모가 날짜를 기준으로 내림차순된 다음에 메모리스트에 저장된다. func fetchMemo(){ let request: NSFetchRequest<Memo> = Memo.fetchRequest() // Core Data가 데이터를 줄때는 데이터가 정렬되어 있지 않다. // 그래서 우리가 직접 정렬해줘야함 // 최신날짜로 정렬 let sortByDateDesc = NSSortDescriptor(key: "insertDate", ascending: false) request.sortDescriptors = [sortByDateDesc] do{ memoList = try mainContext.fetch(request) }catch{ print(error) } }
fetch메소드는 컨텍스트가 제공해주는 메소드를 사용하는데 뒤에 throws가 붙은 메소드는 do catch 구문을 사용해야 한다.
이제 tableView가 memoList에 저장되어 있는 메모를 출력하도록 코드를 변경하자
[ComposeViewController.swfit]
// let newMemo = Memo(content: memo ) // Memo.dummyMemoList.append(newMemo)
기존에 메모를 저장하던 코드 주석 처리
[MemoListViewController.swift]
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { // #warning Incomplete implementation, return the number of rows // return Memo.dummyMemoList.count return DataManager.shared.memoList.count // 테이블뷰가 몇개의 cell을 표시해야하는지 리턴해주는 부분 } // 테이블뷰는 어떤 디자인으로 어떤 내용을 표시해야하는지 알려주는 메소드 // 개별셀을 호출할때마다 이 메소가 호출됨 override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { // cell 이라는 identifier를 가져와서 생성 (이때는 비어있음) let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) // let cell에 우리가 만든 cell이 들어간다 // Configure the cell... // let target = Memo.dummyMemoList[indexPath.row] let target = DataManager.shared.memoList[indexPath.row] // subtitle에는 밑에 두개의 속성이 존재 cell.textLabel?.text = target.content // 이 에러는 insertDate속성이 옵셔널이기 때문에 에러발생 // String from 메소드는 옵셔널 변수를 받을 수 없고, String for로 바꿔줘야 함 cell.detailTextLabel?.text = formatter.string(from: target.insertDate ) // cell.detailTextLabel?.text = target.insertDate.description return cell }
기존의 코드를 위와 같이 수정, 더 dummyMemoList를 호출하는 부분이 있으면 고칠것
이제 정상적으로 빌드되는 것을 확인한 뒤
방금 구현한 fetchMemo 메소드만 호출하면 긑!
[MemoListTableViewController.swift]
// 뷰 컨트롤러가 관리하는 뷰가 화면에 표시되게 직전에 자동으로 호출 // 근데 viewWillAppear 메소드는 full screen에서만 자동으로 호출되고 ios 13이상의 기본값인 sheet에서는 호출되지 않는다 -> notification을 이용해야 한다 override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) DataManager.shared.fetchMemo() tableView.reloadData() // reloadData -> 데이터소스가 전달해주는 최신 데이터로 업데이트 // tableView.reloadData() // print(#function) }
흐음.. 근데 sheet에서는 이 메소드가 호출이 안된다고 했었는데 여기다가 코드를 구현해도 되나...
'iOS' 카테고리의 다른 글
iOS - push 기능 설정 (0) 2020.12.23 iOS - memo 앱 만들기 # 11 DB구현2 (0) 2020.12.16 iOS - memo 앱 만들기 #9 선택 기능과 줄바꿈 구현 (0) 2020.12.15 iOS - memo 앱 만들기 #8 데이터 전달 (0) 2020.12.15 iOS- memo 앱 만들기 # 7 메모 보기2 (0) 2020.12.15