[iOS/Data] CoreData 사용해보기

myungji·2024년 10월 26일

iOS/Data

목록 보기
2/2

이번 포스팅은 아래 글에서 이어지는 내용입니다.
https://velog.io/@myungjilee/iOSData-UserDefaults와-CoreData

지난 글에서는 주로 코어 데이터란 무엇이고 어떤 기능이 있는지를 알아봤다면, 이번 포스팅에서는 코어 데이터를 실제로 사용하는 방법에 대해 알아보자.


CoreData Stack

코어 데이터 스택은 코어 데이터의 핵심 구성 요소들의 집합이다.

NSManagedObjectModel
우리가 정의한 데이터 모델 객체이다.

NSManagedObjectContext
데이터 CRUD를 담당하고, 데이터 모델의 인스턴스를 관리하며 데이터베이스와 상호작용하는 객체이다.

NSPersistentStoreCoordinator
이름에서도 알 수 있듯이 데이터 모델과 실제 데이터 저장소(PersistentStore) 간의 상호작용을 관리한다. 데이터 저장 및 검색을 위한 다양한 작업을 담당하는 객체이다.

NSPersistent Store
데이터를 실제로 저장하는 장소이다.


이제 실제로 동작하는 코드를 살펴보면서 이해해보자!

1. CoreData 모델 만들기

첫번째 단계는 바로 모델 만들기다. 데이터 모델을 생성해 데이터 구조, 타입, 속성, 관계를 정의한다.

새 프로젝트 생성 시 - 'Use CoreData' 체크하기

xCode에서 새 프로젝트를 만들 때 'Use CoreData'를 체크하면 디렉터리에 프로젝트와 같은 이름의 .xcdatamodeld 파일이 생성된다.

기존 프로젝트에 코어 데이터 추가하기

만약 이미 만들어둔 프로젝트에 코어 데이터를 사용하고 싶다면 File > New > File From Templates > iOS 에서 'Data Model'을 생성하면 된다. 혹은 단축키 command + N를 누르면 File From Templates 창이 뜬다.

새 프로젝트 생성 시 'Use CoreData'를 체크했다면 AppDelegate에 아래 코드가 자동으로 들어가게 되지만, 그렇지 않은 경우엔 직접 추가해야 한다. 아래 코드를 복사해서 붙여넣으면 된다!

import UIKit
import CoreData

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    
    lazy var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: "MemoModel")
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        return container
    }()
    
    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)")
            }
        }
    }
}

위 작업은 일반적으로 AppDelegate에서 이뤄지지만 프로젝트의 어느 파일에서 하던 상관없다. 하지만 일반적인 경로는 'AppDelegate'라는 것!

var persistentContainer: NSPersistentContainer = {

NSPersistentContainer는 위 그림에도 나와있듯이 코어 데이터 스택을 관리하는 클래스이다. 모델, 컨텍스트, 저장소 등을 포함한다. 이에 대해서는 아래에서 더 자세히 다뤄보자.

let container = NSPersistentContainer(name: "YourModelName")

name파라미터에는 우리가 정의한 모델 파일의 이름을 넣어주면 된다.

container.loadPersistentStores(completionHandler: { (storeDescription, error) in

loadPersistentStores메서드는 영구 저장소를 로드한다. 비동기적으로 작동하며 completionHandler를 통해 결과를 처리한다.

영구 저장소를 로드하는 게 비동기적으로 작동한다는 게 무엇일까?
loadPersistentStores메서드는 호출된 후 즉시 반환이 되고, 실제 저장소를 로드하는 작업은 백그라운드에서 진행된다.

데이터를 디스크에서 읽고, 필요한 설정을 초기화하는 등의 작업을 백그라운드에서 실행하는 것이다. 그래서 메인 쓰레드가 블로킹되지 않고 사용자가 앱의 다른 기능을 사용할 수 있다.

이 작업이 완료되면 completionHandler가 호출된다. storeDescription에는 비동기적으로 로드된 저장소의 정보가 담긴다.

if let error = error as NSError? {
    fatalError("Unresolved error \(error), \(error.userInfo)")
}

영구 저장소를 로드하는 작업은 비동기적으로 백그라운드에서 진행되고, 컨테이너는 loadPersistentStores 메서드 호출 이후 바로 반환된다. 그래서 비동기 작업이 완료되지 않았더라도 container는 이미 초기화된 상태이다.

그런데 만약 container는 초기화가 완료되어 호출 가능한 상태이지만, 영구 저장소는 로드 중인 상태에서 영구 저장소를 사용하려하면 오류가 발생한다. 그리고 이 오류는 컴플리션 핸들러의 error 파라미터에 담겨 전달된다.

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)")
        }
    }
}

NSPersistentContainer는 모델, 컨텍스트, 저장소 등의 코어 데이터 스택을 관리하는 클래스이다. viewContext는 메인 쓰레드에서 사용할 수 있는 NSManagedObjectContext이다. NSManagedObjectContext는 위에서도 봤지만 혹시 까먹었다면!! 데이터 CRUD를 담당하고, 데이터 모델의 인스턴스를 관리하며 데이터베이스와 상호작용하는 객체이다. 뷰 컨텍스트를 통해 데이터 CRUD를 할 수 있다.

변경 사항이 생기면 .save() 메서드를 실행해 변경 사항을 영구적으로 저장한다.

2. CoreDataManager 파일로 코어 데이터 관리하기

규모가 작은 앱이라면 뷰 컨트롤러에서 코어 데이터를 관리해도 문제가 없다. 하지만 각 뷰컨트롤러에서 따로 코어 데이터를 관리하게 되면 데이터 동기화에 문제가 생긴다.

그래서 앱 전체에서 단일 공유 뷰 컨텍스트(싱글톤 패턴)를 사용하면 데이터 관리가 수월해질 뿐만 아니라 코드의 가독성도 좋아진다.

import Foundation
import CoreData
import UIKit

class CoreDataManager {
    static let shared = CoreDataManager()	// 싱글톤 패턴
    
    private init() {}	// 싱글톤 패턴(외부에서 생성자로 인스턴스 생성 못하게 막음)
    
    private let context: NSManagedObjectContext {
        return persistentContainer.viewContext
    }
}

뷰 컨텍스트를 가져왔다면 이제 데이터 CRUD가 가능하다.

3. viewContext를 통한 데이터 CRUD

간단하게 이름과 나이 속성을 가진 Person 모델을 다루는 사례를 알아보자!

Create

func createPerson(name: String, age: Int16) {
    let person = Person(context: context)
    person.name = name
    person.age = age
    
    do {
        try context.save()
    } catch {
        print("Failed to save: \(error)")
    }
}

Read

func fetchPersons() -> [Person] {
    let fetchRequest: NSFetchRequest<Person> = Person.fetchRequest()
    
    do {
        return try context.fetch(fetchRequest)
    } catch {
        print("Failed to fetch: \(error)")
        return []
    }
}

여기서 NSFetchRequest는 코어 데이터에서 데이터를 조회하기 위해 사용하는 클래스이다. 조건을 설정해 특정 엔티티의 데이터를 가져올 수 있다.

Person.fetchRequest()는 Person 엔티티에 대한 NSFetchRequest<Person> 객체를 반환해서 Person 엔티티의 모든 인스턴스를 조회할 수 있게 한다.

Update

func updatePerson(person: Person, newName: String, newAge: Int16) {
    person.name = newName
    person.age = newAge
    
    do {
        try context.save()
    } catch {
        print("Failed to update: \(error)")
    }
}

Delete

func deletePerson(person: Person) {
    context.delete(person)
    
    do {
        try context.save()
    } catch {
        print("Failed to delete: \(error)")
    }
}

코어 데이터를 사용한다면 번거롭게 페이지간


틀린 내용이 있다면 언제든지 알려주세요~!

참고
https://developer.apple.com/documentation/coredata
https://green1229.tistory.com/428
https://velog.io/@oasis444/CoreData-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0

profile
iOS 초보바리

0개의 댓글