[iOS] Objective-C로 Core Data 시작하기
Core Data는 앱의 모델 객체와 객체 그래프를 관리하는 프레임워크다. 변경 추적, 관계 관리, 검증, Undo/Redo, faulting, 영속성 같은 기능을 제공한다.
Core Data를 단순히 SQLite용 ORM으로 이해하면 중요한 특성을 놓치기 쉽다. SQLite는 사용할 수 있는 저장소 중 하나일 뿐이며, 메모리 저장소처럼 데이터베이스가 아닌 구성도 지원한다.
Core Data 스택
Core Data의 핵심 구성 요소는 다음과 같다.
NSManagedObjectModel: 엔티티, 속성, 관계를 정의한 모델NSManagedObjectContext: 객체를 생성하고 변경 사항을 추적하는 작업 공간NSPersistentStoreCoordinator: 컨텍스트와 영구 저장소를 연결하는 조정자NSPersistentContainer: 위 구성 요소의 생성과 연결을 간소화하는 컨테이너
데이터 모델 파일 이름이 DataModel.xcdatamodeld라면 다음과 같이 컨테이너를 구성할 수 있다.
@interface CoreDataStack : NSObject
@property (nonatomic, strong, readonly) NSPersistentContainer *persistentContainer;
+ (instancetype)sharedStack;
- (void)saveContext;
@end
@implementation CoreDataStack
+ (instancetype)sharedStack {
static CoreDataStack *stack;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
stack = [CoreDataStack new];
});
return stack;
}
- (NSPersistentContainer *)persistentContainer {
if (_persistentContainer == nil) {
_persistentContainer =
[[NSPersistentContainer alloc] initWithName:@"DataModel"];
[_persistentContainer loadPersistentStoresWithCompletionHandler:
^(NSPersistentStoreDescription *description, NSError *error) {
if (error != nil) {
NSLog(@"Core Data 저장소 로드 실패: %@", error);
}
}];
}
return _persistentContainer;
}
- (void)saveContext {
NSManagedObjectContext *context = self.persistentContainer.viewContext;
if (![context hasChanges]) {
return;
}
NSError *error = nil;
if (![context save:&error]) {
NSLog(@"Core Data 저장 실패: %@", error);
}
}
@end
샘플 코드에서는 오류를 로그로 남겼지만, 실제 앱에서는 복구 가능 여부와 사용자 데이터 보호 정책에 맞게 오류를 전달하고 처리해야 한다. 배포 앱에서 무조건 abort()를 호출하는 방식은 피한다.
모델과 NSManagedObject 생성
.xcdatamodeld 편집기에서 엔티티, 속성, 관계를 정의한다. Codegen 설정을 사용하거나 Editor > Create NSManagedObject Subclass로 클래스를 생성할 수 있다.
Employee 엔티티에 대응하는 EmployeeMO 클래스가 있다고 가정하면 객체 생성과 저장은 다음과 같다.
NSManagedObjectContext *context =
[CoreDataStack sharedStack].persistentContainer.viewContext;
EmployeeMO *employee =
[NSEntityDescription insertNewObjectForEntityForName:@"Employee"
inManagedObjectContext:context];
employee.name = @"yep";
NSError *error = nil;
if (![context save:&error]) {
NSLog(@"Employee 저장 실패: %@", error);
}
데이터 조회
NSFetchRequest로 엔티티를 조회하고, 필요하면 predicate와 sort descriptor를 추가한다.
NSFetchRequest<EmployeeMO *> *request =
[EmployeeMO fetchRequest];
request.predicate =
[NSPredicate predicateWithFormat:@"name == %@", @"yep"];
request.sortDescriptors = @[
[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES]
];
NSError *error = nil;
NSArray<EmployeeMO *> *employees =
[context executeFetchRequest:request error:&error];
if (employees == nil) {
NSLog(@"Employee 조회 실패: %@", error);
}
조회 결과가 없는 경우에는 빈 배열이 반환된다. nil은 요청 자체가 실패했다는 뜻이다.
삭제
객체를 컨텍스트에서 삭제한 뒤 저장해야 영구 저장소에 반영된다.
[context deleteObject:employee];
NSError *error = nil;
if (![context save:&error]) {
NSLog(@"Employee 삭제 실패: %@", error);
}
백그라운드 작업
viewContext는 일반적으로 메인 큐에서 UI와 함께 사용한다. 많은 데이터를 가져오거나 수정하는 작업은 별도 컨텍스트에서 수행한다.
NSPersistentContainer *container =
[CoreDataStack sharedStack].persistentContainer;
[container performBackgroundTask:^(NSManagedObjectContext *context) {
// 이 블록 안에서 생성·조회·수정하고 context를 저장한다.
}];
NSManagedObject 인스턴스를 서로 다른 큐의 컨텍스트 사이에서 직접 전달하지 않는다. 대신 NSManagedObjectID를 전달한 뒤 대상 컨텍스트에서 다시 객체를 가져온다.
테스트용 메모리 저장소
단위 테스트에서는 디스크 대신 메모리 저장소를 사용하면 테스트를 빠르고 독립적으로 실행할 수 있다. 저장소를 로드하기 전에 description을 설정해야 한다.
NSPersistentContainer *container =
[[NSPersistentContainer alloc] initWithName:@"DataModel"];
NSPersistentStoreDescription *description =
[NSPersistentStoreDescription new];
description.type = NSInMemoryStoreType;
container.persistentStoreDescriptions = @[description];
[container loadPersistentStoresWithCompletionHandler:
^(NSPersistentStoreDescription *storeDescription, NSError *error) {
NSAssert(error == nil, @"메모리 저장소 로드 실패: %@", error);
}];
정리
Core Data를 사용할 때는 저장소보다 컨텍스트와 객체 그래프를 먼저 이해하는 것이 중요하다. 작은 앱에서는 NSPersistentContainer와 viewContext만으로 시작하고, 작업량과 동시성 요구가 커질 때 백그라운드 컨텍스트와 병합 정책을 추가하는 방식이 관리하기 쉽다.
댓글남기기