NSPersistentContainer를 통한 코어데이터 스택생성하기

macOS Sierra로 업데이트되면서 코어데이터에 NSPersistentContainer 클래스가 추가되었다. 이 클래스를 사용하면 코어데이터 스택을 셋업하는 여러 귀찮은 과정을 생략하고 간단하게 처리할 수 있다. 사실 코어데이터 스택을 수동으로 셋업하는 과정에서 필요한 정보는 코어데이터 모델 파일의 이름과, 저장소 파일을 생성할 위치 정도이며, 그외의 대부분의 코드는 보일러 플레이트라 할 수 있다.  저장소 파일 위치는 적당한이름(?)으로 사용자 라이브러리 내에 만들어지므로 결국 최소한으로 필요한 정보는 데이터 모델 파일 이름이 된다.

수동 셋업 과정은 다음과 같다.

/// Objective-C
@interface AppController: NSObject
@property (readonly, strong) NSPersistentContainer* persistentContainer;
@end

@implmentation AppController
@synthesize persistentConatainer=_persistentContainer;

- (NSPersistentContainer*)persistentContainer
{
  if(!_persistentContainer) {
    _persistentContainer = [[NSPersistentContainer alloc]
                            initWithName: @"MyDataModel"]; // 이름에 확장자는 붙이지 않는다.
    [_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *desc, NSError *error){
       if(!error) {
          /// 저장소 로딩 중 에러가 발생
          NSLog(@"Unresolved error %@, %@", error, error.userInfo);
       }
    }];
  }
  return _persistentContainer;
}

매우 간단하게 모든 설정이 완료됐다. 관리객체모델과 컨텍스트는 managedObjectModel, viewContext 프로퍼티로 액세스할 수 있다. 컨텍스트만 따로 외부로 노출하려한다면, 다음과 같이 프로퍼티를 선언한다.

@property (readonly, nonatomic) NSManagedObjectContext* context;
....
- (NSManagedObjectContext*) context { return self.persistentContainer.viewContext }

저장소 디스크립션을 사용하기

NSPersistentStoreCoordinator를 직접 셋업하는 경우에는 저장소 위치와 타입등의 정보를 설정해야 했다. 이러한 설정정보를 하나의 클래스로 묶은 것이 저장소 디스크립션으로 NSPersistentStoreDescription이라는 클래스로 만들어져있다. NSPersistentContainer는 이 디스크립션을 이용해서 “각각의 저장소들을” 생성한다. 기본적으로 아무런 정보가 주어지지 않으면 컨테이너는 SQLite 타입의 저장소를 라이브러리 디렉토리 내에 저장하게 된다. 만약 저장소 위치를 옮기고 싶거나, 타입을 바꾸고 싶으면 새로운 저장소 디스크립션을 생성해서 바꿔주면 된다. 대신 이 동작이 유효하려면 loadPersistentStores...:를 호출하기 전에 설정을 변경해두어야 한다.

/// 위치를 사용자 문서 폴더로 바꾸고 싶을 때
  NSURL* dirURL = [[[NSFileManager defaultManager] 
                       URLsForDirectory:NSDocumentDirectory
                       inDomains:NSUserDomainMasks] lastObject];
  NSURL* storeURL = [dirURL URLByAppendingPahtComponent:@"mydata.db"];
  NSPersistentStoreDescription* desc = [[NSPersistentStoreDescription alloc] initWithURL:storeURL];
  desc.type = NSSQLiteStoreType;
  [_persistentContainer setPersistentStoreDescriptions:@[desc]];
  [_persistentContainer loadPersistentStoresWithHandler:^ ...

NSPersistentStoreCoordinator를 사용하는 경우에도 -addPersistentStoreWithDescription:completionHandler:를 사용할 수 있으니 참고하자.

Swift 버전 코드

같은 내용인데, Swift 버전의 코드는 아래와 같다.

lazy var container: NSPersistentContainer = {
  let container = NSPersistentContainer(name:"MyDataModel")
  contaner.loadPersistentStore{ desc, error in 
    if error {
      fatalError("Fail to load : \(error)")
    }
}()

var context: NSManagedObjectContext {
  return self.container.viewContext
}

조금 더 깊이

Swift에서 init(name:) 은 편의 이니셜라이저이다. 만약 이 컨테이너를 서브 클래싱할 때 편의 이니셜라이저를 만드려면 지정 이니셜라이저를 호출해야한다. 컨테이너의 지정 이니셜라이저는 init(name:managedObjectModel:) 이므로 전달된 이름을 가지고 관리 객체 모델을 구해서 이를 호출해야 한다. 관리 객체 모델은 수동 셋업때와 같이 모델 파일로부터 로딩해서 생성하면 된다.

convenience init(completionHandler: @escaping () -> ()) {
  guard let mURL = Bundle.main.url(forResource:"MyDataModel", withExtension:"momd")
  else {
    fatalError("Can't load model from bundle.")
  }

  guard let mom = NSManagedObjectModel(contensOf:mURL) else { 
    fatalError("Error initializing MOM")
  }
  init(name:"MyDataModel", managedObjectModel:mom)
  completionHadler()
}

참고 자료