[iPhone][Tutorial #2] SQLite를 Core Data의 default data set으로 이용하기 (pre population)
Aug 19
iphone_tutorial apple, Archive, core data, DB, default data set, iphone, iphone tutorial, iPod, model, NSFetchedResultsController, Persistent Store, Persistent Store Coordinator, Pre population, sqlite, SQLite Manager, Tutorial, Z_METADATA, Z_PRIMARYKEY, 아이팟, 아이폰, 아이폰 튜토리얼, 애플, 튜토리얼 275 Comments
현재 진행 중인 앱엔 Core Data만 사용해도 될 것을 SQLite의 데이터를 Core Data로 import해보고 싶어서 손을 댔다가 며칠을 보냈는지 모른다.
iPhone OS 3.0부터, 애플은 iPhone Dev Center에서 SQLite 관련 Tutorial과 Sample 코드를 내릴 만큼 Core Data를 전격 적으로 밀고 (?) 있다.
iPhone은 그동안 Property list, Settings, plist, Archive 등의 여러 가지 데이터 store, restore 방법을 제공하고 SQLite를 DB로 제공해왔다.
하지만 Core Data에서 object graph를 완성시키고, UI (View)과 data (Model)을 거의 완벽하게 연결해줌으로써 iPhone의 진정한 MVC를 실현 시킨 것 같다 (그동안 dictionary나 array로 table view를 처리했던 일에서 벗어날 수 있게 되었다).
하지만 애석하게도 Core Data는 그 자체만으로는 pre population data set (default data set)을 준비할 수 없다. xml, plist, SQLite 등으로 데이터를 준비해야 한다. 즉, “오늘의 격언”이라는 앱을 만든다고 가정하면, 격언들을 xml이나 plist, SQLite에 저장해두고 앱에서는 Core Data로 그 것을 읽어야 한다. 데이터가 많고 relation이 복잡하며 binary까지 다뤄야 한다면 SQLite를 default data set으로 이용해야 할 것이다. 하지만 애석하게도 이 부분에 대해 자세히 다룬 문서는 애플 dev center의 문서고에서 찾지 못했다.
이 경우 아래 그림과 같이 Persistent Store Coordinator를 이용해서 SQLite의 데이터를 하나의 persistent store로 취급해서 데이터를 가져오고 쓸 수 있다. 이것이 이 Tutorial에서 다룰 내용이다.

서론이 너무 길었다. Tutorial을 시작해보자.
.
Premise
- 이 Tutorial은 앱을 만들 때 Core Data를 만드는 것에 대해서 하나하나 다루지 않는다. 이를 위해서는 Core Data Tutorial for iPhone OS을 보는 것이 좋을 것이다.
- 마찬가지로, TableView 등의 UI에 대해서도 자세히 다루지 않는다.
.
App to make
- AppInfo라는 앱을 만들 것이고 ‘name’, ‘author’, ‘price’의 field를 가지는 SQLite table을 준비해서 이용할 것이다.
- UI는 간단하게 다음과 같이 TableView에 위 정보를 보여줄 것이다.
.
SQLite Manger FireFox pluging 설치
SQLite를 편하게 사용하기 위해서 FireFox Addon인 SQLite Manger를 설치하자. 아주 멋진 녀석인 것 같다. 설명서가 필요 없을 만큼 intuitive한 GUI를 제공하고 table 생성, 데이터 입력뿐만 아니라 query를 직접 날릴 수도 있어 아주 편하다.
SQLite Manger Addon 페이지에서 설치하면 아래 그림과 같이 멋진 녀석이 파폭에 addon된다.

.
AppInfo project 만들기
“AppInfo”라는 프로젝트를 만들자. Use Core Data for storage 옵션 체크를 잊지 말자!
.
SQLite DB 준비하기
사실 상 이 부분이 이 Tutorial의 핵심일 것이다.
AppInfo라는 이름으로 SQLite DB를 만들자. 저장소는 project 폴더 위치로 정한다.
일반적인 SQLite는 Core Data의 persistent store로 사용될 수 없다. Core Data에 import되기 위해서는 다음 두 테이블이 있어야 한다.
M_METADATA, M_PRIMARYKEY
이 부분에 시행착오가 많아서 어떤 사람들은 CoreDataBooks 의 SQLite 파일을 가져와서 수정해서 써라는 사람도 있다.
step 1. Z_METADATA와 Z_PRIMARYKEY table을 만들고 아래와 같이 field를 추가한다.
Z_METADATA는 다음과 같이 만든다.
Z_METADATA
- Z_VERSION: PRIMARY KEY, INTEGER
- Z_UUID: VARCHAR(255)
- Z_PLIST: BLOB
아래 Query를 SQLite Manger에서 바로 실행 시켜도 될 것이다.
CREATE TABLE Z_METADATA (Z_VERSION INTEGER PRIMARY KEY, Z_UUID VARCHAR(255), Z_PLIST BLOB)
M_PRIMARYKEY는 다음과 같이 만든다.
Z_PRIMARYKEY
- Z_ENT: PRIMARY KEY, INTEGER
- Z_NAME: VARCHAR
- Z_SUPER: INTEGER
- Z_MAX: INTEGER
마찬가지로 다음과 같은 Query를 바로 실행시켜도 될 것이다.
CREATE TABLE Z_PRIMARYKEY (Z_ENT INTEGER PRIMARY KEY, Z_NAME VARCHAR, Z_SUPER INTEGER, Z_MAX INTEGER)
step 2. AppInfo table을 만들자.
모든 사용자 table은 Core Data에 import되기 위해서 다음 사항을 지켜줘야 한다.
1. Table 이름은 ‘Z’ prefix를 붙인다. 실제 Core Data의 table 이름에는 Z가 없다. 즉, AppInfo table은 SQLite에서 ZAppInfo로 만들어야 한다.
2. 모든 field도 Z로 시작해야 한다. 즉, Core Data에서는 AppInfo가 ‘name’이라는 attribute를 가져도 SQLite에서는 ‘Zname’으로 field를 맞춰야 한다.
3. 다음 세 field를 가져야 한다.
Z_PK: PRIMARY KEY, INTEGER
Z_ENT: INTEGER
Z_OPT: INTEGER
우리의 AppInfo table은 Zname (VARCHAR), Zauthor (VARCHAR), Zprice (FLOAT)의 field를 가져서 다음과 같은 query 문으로 생성될 수 있을 것이다.
CREATE TABLE “ZAppInfo” (“Z_PK” INTEGER PRIMARY KEY NOT NULL , “Z_ENT” INTEGER, “Z_OPT” INTEGER, “Zname” VARCHAR, “Zauthor” VARCHAR, “Zprice” FLOAT)
GUI를 이용한 모습은 아래와 같다.

step 3. Z_PRIMARYKEY에 다음 record를 넣어줘야 한다.
Z_ENT: ’1′, Z_NAME: ‘AppInfo’, Z_SUPER: ’0′, Z_MAX: ’22′.
아래 그림과 같다.

step 4. AppInfo에 record를 넣는다. primary key를 넣어주는 것을 잊지 말자. 아래와 같이 내가 만든 app들을 넣어봤다. -_-; Z_ENT와 Z_OPT는 비워둔다.

.
UI 뼈대 만들기
IB (Interface Builder)를 직접 사용하지 않고 아래와 같이 코드로 UITableViewController를 만든다. Class 이름은 RootViewController로 하자.
아래는 간단히 과정을 설명한 것이고, 자세한 것은 첨부한 코드를 참조하면 될 것이다. 이것을 다루려는 Tutorial이 아니니…
1. RootViewController를 UITableViewController를 상속받아 만든다.
2. AppInfoAppDelegate에 UINavigationController와 RootViewController를 추가하고 applicationDidFinishLaunching에 아래와 같이 코드를 넣어준다.
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Override point for customization after app launch
rootViewController = [[[[RootViewController alloc] initWithStyle:UITableViewStylePlain] autorelease] retain];
rootViewController.managedObjectContext = self.managedObjectContext;
navigationViewController = [[[[UINavigationController alloc] initWithRootViewController:self.rootViewController] autorelease] retain];
[window addSubview:[self.navigationViewController view]];
[window makeKeyAndVisible];
}
3. RootViewController에는 NSManagedObjectContext와 NSFetchedResultsController를 추가한다. 뒤에서 다루겠지만, NSFetchedResultsController는 정말 멋진 녀석으로 UITableView를 태어났다.
4. 빌드해서 수행하면 빈 Table View가 나타날 것이다.
.
CoreData Model 만들기
SQLite에서 만든 AppInfo와 동일한 scheme를 가진 Model을 만든다. 물론 ‘Z’ prefix는 빼고 만들어야 할 것이다.
Managed Object Class까지 만든 model 은 아래와 같다.

.
Persistent Store Coordinator에서 SQLite DB를 Persistent Store로 가져오기
AppInfo.sqlite 파일을 Project의 “Resouces” 아래에 추가하고, 자동 생성된 AppInfoAppDelegate의 persistentStoreCoordinator method를 아래와 같이 작성한다.
/**
Returns the persistent store coordinator for the application.
If the coordinator doesn't already exist, it is created and the application's store added to it.
*/
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: @"AppInfo.sqlite"];
/*
Set up the store.
For the sake of illustration, provide a pre-populated default store.
*/
NSFileManager *fileManager = [NSFileManager defaultManager];
// If the expected store doesn't exist, copy the default store.
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:@"AppInfo" ofType:@"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
NSError *error;
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
// Update to handle the error appropriately.
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
exit(-1); // Fail
}
return persistentStoreCoordinator;
}
.
NSFetchedResultsController 사용하기
UITableView를 위해 태어난 NSFetchedResultsController를 적용해서 SQLite의 데이터를 TableView에 나타내보자.
Step1. NSFetchedResultsControllerDelegate를 적용한다.
NSFetchedResultsControllerDelegate를 RootViewController가 받아 아래와 같이 fetchedResultsController를 구현한다. Fetch는 AppInfo table을 가지고 올 것이고, name에 따라 sorting할 것이고 section은 사용하지 않아서 아래와 같다.
#pragma mark Fetched results controller
/**
Returns the fetched results controller. Creates and configures the controller if necessary.
*/
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
// Create and configure a fetch request with the Book entity.
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"AppInfo" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
// Create the sort descriptors array.
NSSortDescriptor *nameDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES] autorelease];
NSArray *sortDescriptors = [[[NSArray alloc] initWithObjects:nameDescriptor, nil] autorelease];
[fetchRequest setSortDescriptors:sortDescriptors];
// Create and initialize the fetch results controller. We don't make section.
NSFetchedResultsController *aFetchedResultsController = [[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:nil] autorelease];
self.fetchedResultsController = aFetchedResultsController;
fetchedResultsController.delegate = self;
return fetchedResultsController;
}
Step 2. View가 load 되고 나면 아래와 같이 Core Data에 대해서 fetch를 실행하자.
- (void)viewDidLoad {
[super viewDidLoad];
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
self.title = @"App Info";
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
// Update to handle the error appropriately.
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
exit(-1); // Fail
}
}
Step 3. 아래와 같이 NSFetchedResultsController를 이용해서 간단하게 Table View를 완성하자!
#pragma mark Table view methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [[fetchedResultsController sections] count];
}
// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
id sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
AppInfo *appInfo = [fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = [NSString stringWithFormat:@"%s | %s | $%.2f USD", [appInfo.name UTF8String], [appInfo.author UTF8String], [appInfo.price floatValue]];
cell.textLabel.font = [UIFont systemFontOfSize:13];
return cell;
}
.
모든 과정이 끝났다. 빌드 후 실행 시키면 아래와 같이 SQLite의 DB를 Core Data가 persistent store로 import해서 table view에 DB 내용을 뿌려준다.

.
Source Code
이 Tutorial의 코드는 아래에서 받을 수 있다.
.
Tips
- CoreData는 update 등으로 scheme등이 변경되면 별도의 작업을 해줘야 한다. 즉, 작업 중 persistent store를 제대로 import 해서 XCode가 필요한 entity를 못 찾겠다고 토악질을 하면 Clean 뿐만 아니라 설치된 app도 지우고 다시 빌드하는 것이 좋다. 이것 때문에 정상 코드와 SQLite data를 가지고도 몇 시간 삽질을 했다. -_-;
Ref 1. Core Data Tutorial for iPhone OS
Ref 2. SQLite Tutorial – Saving images in the database
Ref 3. iPhone SQLite Database Basics
Ref 4. Using a Pre-Populated SQLite Database with Core Data on iPhone OS 3.0

Pingback: GuruLinks: 아이폰 앱 76개 소스코드, 강좌, 개발 팁 링크모음 | Guru's Blog
Pingback: 아이폰 앱 76개 소스코드, 강좌, 개발 팁 링크모음 « Mizix