오늘은 URL Session으로 데이터 연결하기에 이어
코어 데이터 생성하여 사용하기에 대해 정리해보려 한다!
코어 데이터는 이번 과제를 통해 처음 사용해보는 거라 조금 어색하고 어려웠지만 무사히 적용할 수 있었다.
CoreData란?
우선 코어데이터가 할 수 있는 것은 데이터의 영구적인 저장이다.
데이터를 디스크에 저장하여 프로그램이 종료된 이후에도 데이터가 남아있게 하고
이것을 영속적인, 영속성, persistent, persistency 등으로 표현한다.
또한 데이터 간 관계 설정이 가능하다.
ex) 학급과 학생의 관계, 부서와 사원의 관계 등
하지만 이번 과제에서는 데이터간 관계 설정의 필요성이 없었기에 설정하진 않았다.
"CRUD" 데이터의 기본적인 처리가 가능하다.
- Create: 데이터 생성 및 저장하기
- Read: 저장된 데이터를 읽어오기
- Update: 저장된 데이터의 내용을 수정하기
- Delete: 저장된 데이터를 삭제하기
→ 다음과 같이 코어데이터를 사용하여 영구저장소에 데이터의 CRUD가 가능하다.
CoreData 사용 설정하기
간단히 코어데이터에 대해 알아봤으니
코어데이터 사용 방법에 대해 알아보자.
데이터 모델 만들기
먼저 프로젝트에서 Command + N을 통해 Data Model 파일을 추가하면 된다. (.xcdatamodeld)
프로젝트 생성 시, Core Data를 선택했다면 해당 단계는 건너뛰면 된다.
파일 추가를 했다면 Entity를 생성해야 한다.
아래 Add Entity 버튼을 클릭해서 Entity의 이름을 변경해주면 된다. (저는 Product로 생성했어요.)
또한 아래 우측에 있는 Add Attribute 버튼을 통해 해당 엔티티에 필요한 속성도 추가해면 된다.
저장하려는 데이터에 맞춰 속성의 Type도 설정해준다.
상품 정보의 id와 title, price이 필요해서 아래와 같이 저장할 정보를 입력했다.
엔티티와 속성까지 추가를 다 했다면 오른쪽 인스펙터에 Class > Codegen > Manual/None를 선택해준다.
이때 Manual/None로 바꿔주지 않으면 Multiple commands produce Error 등 오류가 날 수 있으니 바꿔줍시다!!
다음은 Editor -> Create NSManagedObject Subclass...를 선택하여 파일을 생성해준다.
Core Data Stack 설정
AppDelegate.swift 파일에서 Core Data 스택을 설정할 수 있다.
NSPersistentContainer를 생성할 때 name은,
앞에서 생성한 Data Model의 파일명과 동일하게 입력해 준다.
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "WishList")
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 {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
프로젝트를 만들 때 Use Core Data를 체크했다면 AppDelegate 아래쪽에 위와 같은 코드가 추가된 것을 볼 수 있지만
선택하지 않았다면 해당 코드를 추가해서 name을 바꿔줍시다.
CoreData 사용하기
CoreDataManager 파일에서 코어데이터를 관리할 수 있도록 파일을 따로 만들었다.
Context(NSManagedObjectContext)를 가져오자.
// viewContext 가져오기 --> CRUD
static let context: NSManagedObjectContext? = {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
print("AppDelegate가 초기화되지 않았습니다.")
return nil
}
return appDelegate.persistentContainer.viewContext
}()
여기서 NSManagedObjectContext는 NSManagedObject의 변경사항을 추적하는 클래스이고
NSManagedObject는 우리가 저장할 데이터의 기본 타입이라고 생각하면 된다!
한마디로 CRUD를 하기 위해서는 viewContext를 꼭 가져와야 한다!!
저장하기 (Save)
이제 CoreData에 상품을 저장하는 방법을 알아보자.
let wishProduct = Product(context: context)
wishProduct.id = Int64(product.id)
wishProduct.title = product.title
wishProduct.price = product.price
우선 Product 엔티티 인스턴스를 생성하여
엔티티의 속성에 상품 정보를 설정한다! (예시 데이터로 미리 확인해보자 ex) wishProduct.title = "test1")
do {
try context.save()
} catch {
print("error: \(error.localizedDescription)")
}
이제 저장을 하면 된다.
오류가 날 수 있으니 do - catch 문을 사용해서 저장해준다.
코어 데이터에 저장하는 방법을 알았으니 이제 메서드를 작성해보자!
아래는 상품 정보를 저장하는 메서드의 전체 코드이다.
CoreData에 상품 저장 메서드
// MARK: - SAVE: CoreData에 상품 저장
static func saveWishProduct(product: RemoteProduct, completion: @escaping (Bool) -> Void) {
guard let context = CoreDataManager.context else {
completion(false)
return
}
let wishProduct = Product(context: context)
wishProduct.id = Int64(product.id)
wishProduct.title = product.title
wishProduct.price = product.price
do {
try context.save()
completion(true)
} catch {
print("error: \(error.localizedDescription)")
completion(false)
}
}
원하는 정보를 저장하기 위해
상품 정보와 completion 클로저를 매개변수로 받아 저장 성공 여부를 알 수 있게 했다.
또한 저장이 성공했는지 여부를 비동기적으로 알 수 있기에 저장 작업을 호출한 쪽에서 적절한 처리를 할 수 있다.
저장 메서드 호출
// MARK: - 위시 리스트 담기 Btn
@IBAction func tappedSaveProductButton(_ sender: UIButton) {
guard let product = currentProduct else { return }
print("위시리스트 담는 상품: \(product)")
CoreDataManager.saveWishProduct(product: product) { success in
if success {
print("상품이 위시 리스트에 추가되었습니다.")
} else {
print("상품을 위시 리스트에 추가하는 데 실패했습니다.")
}
}
}
코어 데이터에 원하는 상품 정보를 저장하기 위해
위시리스트 담기 버튼을 눌렀을 때 위의 saveWishProduct메서드를 호출해주면 저장이 잘 되는 걸 확인할 수 있다.
이렇게 해당 상품을 위시리스트 목록에 추가하는 과정이 끝났다.
이제 위시리스트 목록을 불러오는 코어데이터를 읽는 방법을 알아보자!
읽기 (Read)
저장이 잘 되었는지 확인하고
저장된 데이터를 불러오기 위해 코어데이터를 조회하는 읽기 과정이다.
fetchRequest는 Core Data에서 객체를 검색하는 데 사용되기 때문에 생성해 준다.
조회한 상품 정보를 화면에 보여주기 위해 반환 타입도 설정했다!
CoreData의 상품 조회 메서드
// MARK: - READ: CoreData에서 상품 정보 불러오기
static func fetchCoreData() -> [Product] {
guard let context = context else { return [] }
let fetchRequest = NSFetchRequest<Product>(entityName: entityName)
do {
let productList = try context.fetch(fetchRequest)
productList.forEach {
print($0.id)
print($0.title ?? "title")
print($0.price)
}
return productList
} catch {
print("코어 데이터 fetch error: \(error.localizedDescription)")
return []
}
}
읽기 메서드 호출
func loadWishList() {
wishList = CoreDataManager.fetchCoreData()
tableView.reloadData()
}
나는 테이블뷰에서 데이터를 불러와야 하기 때문에
테이블 뷰의 기본적인 세팅을 마치고 데이터를 불러오는 메서드인 fetchCoreData()를 호출해준다.
func bind(_ product: Product) {
idLabel.text = "[\(product.id)]"
titleLabel.text = product.title
priceLabel.text = product.price.formatAsCurrency()
}
위의 코드는 테이블 뷰의 셀에 해당 데이터를 설정해주는 내용이다.
삭제하기(Delete)
추가 구현이었던 코어데이터를 삭제하는 과정도 알아보자!
데이터의 id를 파라미터로 받아 삭제하는 코드이다.
CoreData 삭제 코드
// MARK: - DELETE: CoreData에서 상품 삭제
static func deleteProduct(withId id: Int64, completion: @escaping (Bool) -> Void) {
guard let context = CoreDataManager.context else {
completion(false)
return
}
let fetchRequest = NSFetchRequest<Product>(entityName: CoreDataManager.entityName)
fetchRequest.predicate = NSPredicate(format: "id == %lld", id)
do {
let products = try context.fetch(fetchRequest)
for product in products {
context.delete(product)
}
try context.save()
completion(true)
} catch {
print("Error deleting product: \(error.localizedDescription)")
completion(false)
}
}
저장하는 것과 마찬가지로 completion 클로저를 매개변수로 받아 삭제가 성공했는지 여부를 비동기적으로 알 수 있다.
삭제할 상품을 찾기 위한 NSFetchRequest를 생성하여 조건을 설정해준다.
상품의 id와 주어진 id가 일치하는지 확인하여 삭제되는 과정이다.
삭제 메서드 호출
// MARK: - cell 삭제
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
// 코어 데이터에서 데이터에서 셀 삭제
let selectedProduct = wishList[indexPath.row]
let productId = selectedProduct.id
// delete 사용
CoreDataManager.deleteProduct(withId: productId) { success in
if success {
print("상품 삭제 성공")
self.loadWishList()
} else {
print("상품 삭제 실패")
}
}
}
}
이제 테이블 뷰에서 스와이프를 해서 삭제해주도록 하자.
해당 셀의 id를 가져와 코어데이터에서 그 id에 맞는 데이터를 삭제해주면 된다.
테이블 뷰의 보여지는 셀을 지워줄 필요는 없이 테이블뷰를 다시 로드해주자.
셀이 잘 삭제되었을 때 테이블뷰가 리로드된다.
아래와 같이 작성하면 코어데이터와 테이블뷰에서 잘 삭제되는 걸 확인할 수 있다~!
CoreData 저장, 읽기, 삭제 전체 코드
결과 화면
CoreDataManager
class CoreDataManager {
static let entityName = "Product"
// viewContext 가져오기 --> CRUD
static let context: NSManagedObjectContext? = {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
print("AppDelegate가 초기화되지 않았습니다.")
return nil
}
return appDelegate.persistentContainer.viewContext
}()
// MARK: - SAVE: CoreData에 상품 저장
static func saveWishProduct(product: RemoteProduct, completion: @escaping (Bool) -> Void) {
guard let context = CoreDataManager.context else {
completion(false)
return
}
let wishProduct = Product(context: context)
wishProduct.id = Int64(product.id)
wishProduct.title = product.title
wishProduct.price = product.price
do {
try context.save()
completion(true)
} catch {
print("error: \(error.localizedDescription)")
completion(false)
}
}
// MARK: - READ: CoreData에서 상품 정보 불러오기
static func fetchCoreData() -> [Product] {
guard let context = context else { return [] }
let fetchRequest = NSFetchRequest<Product>(entityName: entityName)
do {
let productList = try context.fetch(fetchRequest)
productList.forEach {
print($0.id)
print($0.title ?? "title")
print($0.price)
}
return productList
} catch {
print("코어 데이터 fetch error: \(error.localizedDescription)")
return []
}
}
// MARK: - DELETE: CoreData에서 상품 삭제
static func deleteProduct(withId id: Int64, completion: @escaping (Bool) -> Void) {
guard let context = CoreDataManager.context else {
completion(false)
return
}
let fetchRequest = NSFetchRequest<Product>(entityName: CoreDataManager.entityName)
fetchRequest.predicate = NSPredicate(format: "id == %lld", id)
do {
let products = try context.fetch(fetchRequest)
for product in products {
context.delete(product)
}
try context.save()
completion(true)
} catch {
print("Error deleting product: \(error.localizedDescription)")
completion(false)
}
}
}
'TIL✏️' 카테고리의 다른 글
[iOS] UICollectionView: Header(헤더) 사용하기 (1) | 2024.04.23 |
---|---|
[iOS] WishList App - ScrollView 적용, Pull to Refresh (4) | 2024.04.18 |
[iOS] WishList App - URLSession 으로 API 연결하기 (2) | 2024.04.16 |
[iOS] 키오스크 앱 프로젝트 - 주문 내역 화면 (1) | 2024.04.03 |
[iOS] 키오스크 앱 프로젝트 - 스토리보드 초기 뷰 컨트롤러 설정 (1) | 2024.04.02 |