2014년 3월 4일 화요일

[iOS/OSX] CoreData #1 기본

CoreData를 명확하게 설명할 말은 찾기 힘든 것 같다. 쉽게 보면 '로컬 데이터베이스를 쉽게 쓰게 해 주는 도구' 로 설명 할 수도 있고 약간 넓게 'ORM 프레임워크'로 설명 할 수도 있다. 물론 이 모두 CoreData를 완벽하게 설명해 주는 말은 아니다.

어쨌든, CoreData를 사용하면 앱에서 사용하는 로컬 데이터베이스(sqlite3 기반)를 SQL 없이 다룰 수 있다. 참고로 좀 귀찮은 부분이 많다.

프로젝트 생성

CoreData를 이용하기 위해선 미리 프로젝트를 생성 할 때 Empty Project를 골라서 CoreData를 사용하겠다는 박스에 체크를 해서 생성하는 것을 추천한다. 나중에 별도로 추가 할 수도 있지만 좀 귀찮다.


이렇게 프로젝트가 생성되면 AppDelegate 쪽에 평소에는 보지 못 하던 코드들이 자동으로 들어간다. (수동으로 추가하면 이런 코드들을 전부 수동으로 넣어줘야 하기 때문에 귀찮다는 것이다)

AppDelegate.h 파일에 보면
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;

- (void)saveContext;
- (NSURL *)applicationDocumentsDirectory;
이런 내용들이 추가되어 있다.

여기서 제일 위의 컨텍스트를 제외하면 나머진 크게 사용되진 않으므로 그냥 신경쓰지 않아도 된다.

컨텍스트(NSManagedObjectContext)는 단순히 데이터베이스 연결이라고 생각하면 속편하다. 이걸로 쿼리를 하거나 레코드를 추가(INSERT)한다거나 등등을 할 수 있다. 그래서 데이터베이스 접근이 필요한 곳은 이 컨텍스트를 접근 할 수 있게 만들어야 한다. 개인적으론 이 컨텍스트를 가지고 다루는 싱글톤(Singleton) 객체를 하나 만들어 두고 이 녀석이 DB를 담당하도록 만드는 것을 선호한다.

AppDelegate.m 파일도 몇 가지 추가되는 코드들이 있는데 일단 신경쓰지 않아도 모두 기본적으로 사용할 준비가 갖춰진 상태의 코드들이다. 그래서 코드 내용 설명은 생략한다.

데이터베이스 스키마 만들기

프로젝트에 보면 프로젝트명.xcdatamodeld 라는 파일이 추가되어 있다. 이 파일이 데이터베이스 스키마를 담고 있는 파일이다. 클릭해서 열어보면 아래와 같은 화면이 나타난다.


용어가 일반적인 DB용어와는 다르다. 단순히 이야기 해서 Entity는 Table이다. Entity를 추가한다는 것은 SQL의 CREATE TABLE 명령어와 비슷하다. Relationships는 SQL에서 ForeignKey 나 JOIN 등에서 사용하는 것과 비슷한데 여기서는 생략한다.

Entity 에는 Attibutes와 Relationships 등을 만들어 줄 수 있다. 여기서 Attributes 가 바로 필드(FIELD)이다. Entity의 Attibutes를 만드는 것이 곧 Table Scheme 을 만드는 것이다.


에제로 TestEntity 라는 엔티티를 하나 만들어서 속성 들을 정의했다.

ORM 스타일로 사용하기

엔티티를 만들어 놓고 그냥 쓸 수도 있지만 여기서는 ORM 스타일로 엔티티에 매핑되는 모델(Model) 클래스를 만드는 예제를 설명한다.

위의 .xcdatamodeld 창을 열어둔 상태에서 메뉴의 Editor - Create NSManagedObject Subclass... 이라는 것을 선택하면 모델 클래스를 Xcode가 알아서 생성해 준다. (참고로 File - New 메뉴를 이용해서 ManagedObject를 생성 하는 것도 비슷하다)


원하는 모델을 체크하고 Next 버튼을 눌러서 진행하자. 프로젝트에 해당 엔티티의 이름과 동일한 파일 2개(.h와 .m 파일)가 추가된다.

파일의 내용을 보면 좀 전에 만들었던 엔티티의 구성과 동일한 프로퍼티가 정의되어 있음을 알 수 있다.

레코드를 삽입(INSERT)해 보자

우선은 테스트용 데이터가 필요하다. 아래는 INSERT 예제이다.
TestEntity *entity = [NSEntityDescription insertNewObjectForEntityForName:@"TestEntity" inManagedObjectContext:self.context];
entity.when = [NSDate date];
entity.what = @"Test Record";
entity.done = [NSNumber numberWithBool:NO];

NSError *error = nil;
if ([self.context save:&error] == NO) {
    NSLog(@"Failed to add entity: %@", [error description]);
}
엔티티 이름을 문자열로 넘겨주는게 약간 독특한데 CoreData에서는 문자열로 뭔가를 명시하는 경우가 많다. 그러려니 하자.

self.context는 위의 AppDelegate에 생성된 managedObjectContext를 그대로 가져온 것이다. NSEntityDescription 클래스를 이용해 이 컨텍스트에 새로운 오브젝트를 삽입(insert) 하기 위한 TestEntity 클래스의 오브젝트를 만들어 낸다. 그리고 데이터를 집어 넣고 컨텍스트의 save 메서드를 호출하면 실제로 오브젝트(레코드)가 엔티티(테이블)에 기록된다.

참고로 여기서 TestEntity를 NSEntityDescription을 이용하지 않고 그냥 alloc/init 하는 방식으로 생성하면 제대로 동작하지 않는다.

레코드를 몽땅 읽어보기

아래는 엔티티의 모든 오브젝트를 가져오는 예제이다.
NSFetchRequest *fetch = [[NSFetchRequest alloc] init];
fetch.entity = [NSEntityDescription entityForName:@"TestEntity"
                           inManagedObjectContext:self.context];

NSError *error = nil;
NSArray *objects = [self.context executeFetchRequest:fetch error:&error];
if (error) {
    NSLog(@"Failed to fetch objects: %@", [error description]);
}
문제 없이 실행되었다면 objects 라는 array에 TestEntity 타입의 모든 레코드 오브젝트가 들어있다고 보면 된다. 실제론 오브젝트 자체를 액세스 하기 전 까지 실제 데이터는 로드되지 않지만 개발자는 딱히 신경쓰지 않아도 된다.

관련​포스트:

댓글 없음 :