코어데이터의 기본 개념

코어데이터에 대한 개념 정리를 좀 해보자.

코어데이터는 고정된 저장소와 메모리상의 스크래치 보드를 연결하여, 마치 어떤 데이터 클래스 객체들의 배열을 다루는 코드로 데이터를 생성, 편집, 삭제하며 저장할 수 있는 기능을 제공하는 프레임워크이다. 코어 데이터 내에서 사용되는 데이터 클래스들은 각각의 프로퍼티에 대해 확장된 KVC/KVO를 사용하는 것으로 최대한 유연하게 동작하기 때문에 사실상 개별 데이터의 클래스에 구애받지 않는 범용 API들을 설계해두고 있다.

따라서 비록 정확하지는 않지만 코어데이터는 DB액세스를 OOP 형식으로 만들어놓았다고 생각하고 사용할 수 있다. 이 관점에서 바라볼 때 코어데이터의 데이터 모델에는 세 가지의 기본적인 개념이 있다. 그것은 엔티티와 속성(attributes) 그리고 관계(relationships)이다.

엔티티

엔티티는 DB의 테이블 스키마에 해당된다. 엔티티는 고유의 이름으로 다른 엔티티와 구분된다. 엔티티는 그 내부에 속성과 관계를 정의하고 있다. 속성(attributes)은 클래스의 프로퍼티에 해당하며, 해당 엔티티의 레코드가 가지는 속성값을 말한다. 관계(relationships)은 엔티티가 다른 엔티티를 참조하는 것으로 클래스에서는 다른 타입의 객체 혹은 객체 배열에 대한 참조에 해당한다. 즉 엔티티 그 자신은 하나의 테이블인 동시에 클래스 하나로 번역되고 속성과 관계는 모두 클래스 상에서의 프로퍼티에 해당하며, 속성인 경우에는 데이터를 저장하며, 관계인 경우에는 다른 엔티티가 나타내는 클래스에 대한 참조로 번역된다.

엔티티 내의 개별 데이터, DB로 치면 테이블 내의 레코드 1개에 해당하는 값을 관리되는 객체(managed object, 편의상 이후 ‘관리객체’라 하겠다.)라고 한다. 관리 객체의 원형은 NSManagedObject라는 클래스이며, 코어데이터에서는 각 엔티티마다 그 구조를 따라 모델링되는 NSManagedObject 의 서브 클래스를 자동으로 만들어 낸다. 즉, 하나의 레코드 값은 NSManagedObject의 서브 클래스의 인스턴스가 되며, 해당 서브 클래스의 모든 속성과 관계는 코어데이터가 자동으로 생성해준다. 이는 컴파일 타임에 자동으로 생성하는 것도 가능하지만, 커스터마이징을 위해서 미리 생성하여 프로젝트에 파일 형태로 포함할 수도 있다.

관리객체 (Managed Object)

관리객체는 코어데이터에 의해서 관리되는 데이터 객체라는 의미이다.  앱에서 생성되거나 저장소로부터 로드된 데이터는 메모리상에서 이 관리객체 인스턴스가 된다. 관리 객체 인스턴스는 NSManagedObject 클래스의 인스턴스이다. 프로젝트 내에서 각 엔티티마다 NSManagedObject의 서브 클래스를 작성(혹은 Xcode가 생성)하기는 하지만, 이는 사실 특별한 비즈니스 로직이 필요하거나 하는 등의 경우에 필요할 뿐이다. 기본적으로 관리 객체는 자신의 속성에 관심이 없다. 관리 객체는 단지 “하나의 테이블 레코드”를 의미하는 바 그 이상도 이하도 아니며, 여기서 중요한 것은 코어데이터가 관심있어 하는 것은 특정한 레코드가 로딩된 이후에 변경/삭제 되었거나 혹은 새로운 관리 객체가 삽입되었냐 하는 것이다. 물론 하나의 관리 객체 인스턴스는 저장된 값을 액세스할 수 있어야 하며, 이는 클래스의 구조보다는 KVC를 통하여 일종의 사전처럼 속성 이름을 사용해서 그 값을 간접적으로 액세스한다.

따라서 NSManagedObject의 서브 클래스에 의해서 해당 객체의 프로퍼티가 정의된다고 생각하면 안된다. 관리객체의 프로퍼티는 접근자는 물론, 인스턴스 저장공간까지 모두 코어데이터에 의해서 자동으로 관리된다. NSManagedObject의 서브 클래스가 만들어지는 것은 오로지 “코드 상에서 KVC를 통해서 특정한 속성/관계를 쉽게 액세스하기 위해서”일 뿐이다. 관리 객체와 엔티티의 관계에 대해서는 뒤에서 추가적으로 설명하겠다.

모델, 관리되는 객체 모델(Managed Object Model)

관리되는 객체 모델(이후, 편의상 관리객체 모델)은 데이터베이스 전체의 스키마에 해당한다. 엔티티가 단일 데이터의 스키마라고 할 때, 모델은 그 내부의 모든 엔티티와 둘 이상의 테이블간의 관계에 이르는 전체 내용을 포괄한다. 하나의 모델에는 두 개 이상의 엔티티가 있을 수 있고, 하나의 앱은 두 개 이상의 모델을 사용할 수 있다.

관리 객체 모델은 코드상으로 편집하는 것도 가능하지만, 어차피 컨텍스트나 저장소 코디네이터와 연결된 후에는 변경이 불가하므로 이렇게 변경할 일은 거의 없다고 보면 된다. 대신에 거의 대부분의 경우, 관리 객체 모델은 코어데이터 편집기를 통해서 미리 정의해둔 코어데이터 모델 파일(momd)을 읽어들여서 생성하는 방식으로 사용한다.

데이터 베이스가 단순한 테이블의 집합이라면, 모델은 엔티티들의 집합인 동시에 각 엔티티가 어떤 속성을 가지고 있으며, 엔티티끼리는 어떤 관계를 가지고 있는지에 대한 정보를 담고 있다. 즉 각각의 엔티티에 의한 관리객체 내부가 어떻게 생겼는지에 대해서는 코어데이터는 관리객체 클래스보다는 이 모델을 통해서 파악하게 된다.

영구 저장소 코디네이터 (Persistent Store Coordinator)

영구 저장소 코디네이터는 (이하 줄여서 코디네이터라고 부른다.) 데이터베이스에 대한 I/O를 담당하는 부분이다. 코디네이터는 이후 기술할 컨텍스트의 요청을 받아 필요한 데이터를 읽고 조회하여 올려주고, 컨텍스트가 내려준 변경사항을 파일에 저장한다. 영구저장소로는 XML 파일이나 SQLite 데이터 베이스를 사용할 수 있으며, 하나의 코디네이터는 한 개 이상의 저장소를 동시에 관리할 수 있다. (예전에는 이런 방식으로 입출력 속도를 높였는데, 지금은 입출력과 관련한 병렬처리 지원이 되어서 1개로도 충분!)

영구 저장소 코디네이터는 사실 단순 입출력을 담당하고 있으며, 실제 저장소 파일이나 DB의 구조에 대해서는 스스로 처리하지 못한다. 이러한 부분은 전적으로 관리 객체 모델에 의존해야 하며, 따라서 코디네이터를 생성하기 위해서는 관리객체모델이 필수적으로 필요하다.

컨텍스트 – 관리 객체 컨텍스트 (Managed Object Context)

관리 객체 컨텍스트는 (이하 줄여서 컨텍스트라고 부른다.) 일종의 코르크보드 같은 게시판으로 생각할 수 있다. 그리고 코어데이터에서 가장 중요하게 여겨지는 중심 객체이다. 코어데이터에서 모든 데이터는 이 컨텍스트 상에서 관리 객체로 생성되거나, 저장소로부터 로딩해와서 올려놓을 수 있다. 컨텍스트 상의 모든 관리 객체의 생성, 제거, 변경은 컨텍스트에 의해서 모두 추적/관리된다.

컨텍스트는 현재 상태의 변경 사항을 기록해두기 위해서 영구 저장소 코디네이터를 필요로 한다.

영구 저장소 컨테이너 (Persistent Container)

Xcode8에서부터 NSPersistentContainer가 새롭게 추가되었다. 이전까지의 코어데이터 스택을 구성하는 코드들은 사실상 코어데이터 모델 파일의 이름만 다를 뿐, 거의 모든 내용이 동일했기 때문에, 스택 셋업을 편리하게 할 수 있는 요건을 제공한다. 또한, 컨테이너를 사용하는 경우, viewContext라는 프로퍼티를 통해서 컨텍스트 역시 이미 셋업된 상태로 얻을 수 있다. 그외에 몇 가지 추가적인 편의 메소드들을 제공해주고 있다.

정리

이상으로 코어데이터에서 사용되는 개념 및 주요 클래스들을 살펴보았다.  이들에 대한 내용을 핵심만 간단하게 다시 추려보자면 다음과 같다.

  1. 데이터를 조회하거나 삽입, 삭제하는 것은 컨텍스트를 사용해서 처리한다.
  2. 개별 데이터는 NSManagedObject의 (혹은 그 서브 클래스의) 인스턴스로 표현된다. 이 때 서브클래스는 코드상에서 개별 데이터 내의 속성 및 관계을 쉽게 액세스하기 위해서 필요하며, 실제 데이터 액세스 과정은 코어데이터 런타임에 의해서 KVC를 통해 이뤄진다.
  3. 개별 데이터의 실제 구성은 엔티티에 의해 정의된다. 엔티티는 DB의 테이블 스키마에 해당되는 개념이며, 코드상에서는 이름으로만 노출된다.
  4. 엔티티들의 집합은 관리객체모델이다. 모델은 코어데이터 모델편집기를 통해서 그 내부에 들어갈 엔티티들을 정의하게 되고, 이는 .momd 패키지로 앱번들에 포함된다. 모델은 일단 코어데이터 모델 편집기를 통해서 생성한 후에는 코어데이터 내부 알고리듬에 의해 참조되며, 개발자가 실제로 모델을 조작할 일은 거의 없다.
  5. 컨텍스트에서 데이터를 추가/변경한 내용을 실제로 디스크에 입출력하는 부분은 저장소 코디네이터에 의해 수행된다. 코어데이터 API의 상부는 데이터 모델에 별 관심이 없으며, 오직 저장소 코디네이터가 모델을 참조하여 데이터를 올바른 위치에 쓰거나, 읽게 된다.

결국 코어데이터를 앱에 적용하기 위해서는 Xcode 프로젝트에서 코어데이터 모델 편집기를 통해서 모델 파일을 생성하고, 모델 파일의 이름으로부터 코어데이터 스택을 수동으로 생성/초기화하거나 컨테이터를 생성하여 코어데이터를 활성화할 수 있다. 코어데이터 스택이 만들어진 다음부터는 컨텍스트를 이용해서 저장된 데이터를 fetch하거나 신규로 생성된 데이터를 삽입할 수 있다. 이후의 디스크 입출력과 관련된 부분은 코어데이터 스택이 알아서 관리하며 (명시적으로 컨텍스트의 변경사항을 저장하는 것은 가능하다.) 추가적인 몇 가지 컨트롤러 클래스등과 연계한다면 작성해야 하는 코드를 비약적으로 줄일 수 있다.

예컨데, macOS용 앱을 만드는 경우에 문서기반앱으로 코어데이터를 포함하여 프로젝트를 시작하고 UI와 접합점에 코코아 바인딩을 적용하는 경우, 간단한 데이터 관리 앱을 코드 1줄 쓰지 않고 만드는 것도 가능하다. 코어데이터가 가장 좋은 DB추상화 레이어라고 말할 수는 없지만 애플의 다른 기술들과 연계하였을 때에는 매우 강력한 도구로 쓰일 수 있을 것이다.

참고자료