CoreData란?
CoreData는 Apple에서 제공하는 객체 그래프(Object Graph) 및 영속성(Persistence) 관리 프레임워크입니다.
SQLite를 기반으로 로컬 데이터베이스로도 사용할 수 있지만, 단순히 데이터베이스 기능뿐만 아니라 객체 간의 관계 관리, 상태 추적, 변경 감지, iCould 연동, Undo/Redo, Lazy Loading 등 다양한 기능을 제공합니다.
객체 그래프란?
객체 그래프란 참조를 통해 서로 연결되어 있는 객체들의 구조를 말합니다.
아래 코드 예시를 보면 Person과 Dog는 서로 참조를 가지고 있고, 이를 도식화하면 그래프 형태로 나타낼 수 있습니다.
CoreData는 이 객체들 사이의 관계를 정의하고, 이를 저장소에 관계형 데이터처럼 변환하여 저장합니다.
class Person {
var name: String
var dog: Dog?
}
class Dog {
var name: String
weak var owner: Person?
}
CoreData의 저장 구조
CoreData의 저장 구조는 아래와 같은 계층 구조를 가집니다.
NSManagedObject // DB에 저장될 객체
⬇️
NSManagedObjectContext // 변경 사항을 추적하는 작업 공간
⬇️
NSPersistentStoreCoordinator // 저장소와 Context를 연결
⬇️
저장소 (SQLite, CloudKit, In-Memory 등)
NSManagedObject: CoreData를 통해 자동으로 생성 및 관리되는 객체로, 속성 및 관계를 포함하며 DataBase의 Scheme 역할을 합니다.
NSManagedObjectContext: 트랜잭션 단위의 작업 공간으로, 변경사항을 추적하고 save() 를 통해 저장할 수 있습니다.
NSPersistentStoreCoordinator: 실제 저장소와 Context를 연결하는 역할을 합니다.
NSPersistentContainer는 CoreData Stack을 구성하는데 필요한 객체들을 담는 역할을 합니다. CoreData를 설정할 때 주로 사용됩니다.
NSManagedObject 생성
MyModel.xcdatamodeld
라는 CoreData Model을 만든 다음,
Entites 에 저장하고 싶은 객체를 모델링하면
NSManagedObject가 자동으로 생성되고, 코드에서 참조할 수 있게 됩니다.
멀티스레드 환경에서 CoreData 다루기
CoreData에서 이뤄지는 트랜잭션들은 NSManagedObjectContext를 통해 이뤄지는데, Context는 기본적으로 메인스레드에서 실행됩니다.
따라서 대량의 데이터 처리나 저장 작업을 백그라운드에서 처리하지 않으면 메인스레드가 버벅이게 되어 UX에 영향을 줄 수 있습니다.
NSManagedObjectContextConcurrencyType
이를 위해 CoreData는 Context를 초기화할 때 NSManagedObjectContextConcurrencyType
옵션을 제공합니다.
옵션으로 .mainQueue
또는 .privateQueue
를 사용할 수 있고,
.mainQueue 는 메인 스레드에서 .privateQueue는 백그라운드 스레드에서 실행되므로 UI와 관련된 작업만 mainQueue에서 수행하는게 좋습니다.
또는 newBackgroundContext() 메소드를 통해 내부적으로 .privateQueueConcurrencyType으로 설정된 Context를 생성할 수도 있습니다.
let backgroundContext = persistentContainer.newBackgroundContext()
.mainQueueConcurrencyType으로 설정된 viewContext도 생성할 수 있습니다.
let viewContext = persistentContainer.viewContext
perform, performAndWait
Context에서의 작업은 perform 또는 performAndWait을 통해 안전하게 실행할 수 있습니다.
perform은 비동기, performAndWait은 동기 방식으로 동작합니다.
큐에 작업이 등록되고 순차적으로 실행되어 데이터 레이스 문제를 방지할 수 있습니다.
// 비동기
backgroundContext.perform {
let entity = SomeEntity(context: backgroundContext)
entity.title = "Test"
try? backgroundContext.save()
}
// 동기
backgroundContext.performAndWait {
let entity = SomeEntity(context: backgroundContext)
entity.title = "Test"
try? backgroundContext.save()
}
MergePolicy
멀티스레드 환경에서 서로 다른 NSManagedObjectContext가 동일한 객체를 수정하고 저장하려고 하면 충돌(Conflict)이 발생합니다.
func saveAsync() {
let viewContext = container.viewContext // 메인 스레드
let bgContext = container.newBackgroundContext() // 백그라운드 스레드
// 동일한 객체 참조
let object1 = try? viewContext.existingObject(with: id) as? SomeEntity
let object2 = try? bgContext.existingObject(with: id) as? SomeEntity
// 수정
object1.title = "title1"
object2.title = "title2"
try? viewContext.save() // 저장됨
bgContext.perform {
// bgContext의 스냅샷과 db의 객체 상태가 다름을 감지
// NSMergeConflict 발생
// mergePolicy가 .error(기본값)이므로 저장 실패
try? bgContext.save()
}
}
이러한 문제를 해결하기 위한 정책이 MergePolicy입니다.
MergePolicy를 설정하지 않으면 기본값은 .error로 충돌이 발생했을 때 저장에 실패합니다.
MergePolicy 종류는 아래와 같습니다.
.error
: 충돌 시 저장 실패.mergeByPropertyObjectTrump
: 현재 Context 값으로 덮어쓰기 (충돌된 속성만).mergeByPropertyStoreTrump
: DB 값이 우선.overwrite
: 현재 Context 값으로 덮어쓰기 (객체 전부)
.rollback
: 현재 Context의 변경사항을 모두 되돌림
'iOS' 카테고리의 다른 글
번역) SwiftUI 커스텀 뷰 선언하기 (0) | 2025.06.06 |
---|---|
애플 로그인 서버부터 클라이언트까지(Swift + Nest.js + TypeScript) (1) | 2025.05.22 |
Alamofire 기능 정리 (0) | 2025.05.12 |
테스트 가능한 오디오 관리 객체 설계하기 (0) | 2025.04.17 |
AVFAudio - AVAudioSession (1) | 2025.03.29 |