최초 작성일 : 2010/09/20 00:33 



애초의 계획은 일정 내용이 진행되면 해당 내용에 관련된 소스 코드 전체를

공개하고 이후 다음 진행하고 또 관련 소스 공개하고...이런 식으로 하려 했는데

소스를 부분부분 자르려니 쉽지도 않고 또 보시는 입장에서도 단편적인

소스는 이해하기가 어려울 것 같아 소스 공개는 이 [실전 소스 분석]이 모두

끝난 후 전체 소스를 한꺼번에 공개하는 것으로 방향을 잡았습니다.

이 점 참고하시기 바랍니다.


1. Predicate 사용하기


지난 시간까지 코어 데이터의 개념, 필요한 메서드들, 테이블 뷰 컨트롤러를 통한 코어 데이터의

사용 등에 대해 알아보았습니다. 사실 기본 개념이나 원리를 몰라도  API 내용만을 가지고도

충분히 사용할 수 있을만큼 코어 데이터의 사용법은 간단합니다. 더불어 테이블 뷰와의 관계도

잘 만들어져 있어 NSFetchedResultsControllerDelegate에 선언된 메서드를 구현하면 별다른

코딩 없이 코어 데이터의 변경사항(입력, 수정, 삭제)이 즉시 테이블 뷰에 반영이 됩니다.

개발자에게는 정말로 편한 방법이라고 할 수 있을 것입니다.


하지만 항상 테이블의 모든 데이터를 화면에 출력하는 것은 아닙니다. 특정 날짜에 등록된

데이터, 혹은 특정 성씨를 가진 사람들의 집합. 또는 득정 지역별 데이터 등 어떤 조건에 따라

전체 데이터 중 일부만을 필요로 하는 경우가 대다수일 것입니다. 이 때 사용하는 것이

Predicate이고 이 것은 RDBMS에서 SQL문을 작성할 경우 where 조건절에 해당하는 내용을

담게 됩니다.


Predicate 역시 클래스를 사용하는 것 자체는 어렵지 않습니다. 다만 인자로 넘기는 Prediccate의

포맷을 잘 알아두어야 합니다. 가끔 언급을 했듯이 제가 진행하는 이 내용은 '강좌'라기보다는

제 소스를 같이 분석해보는 '소스 분석'에 가깝기 때문에 이러한 세세한 부분까지는 다루지

않겠습니다. 단지 Predicate 클래스를 어떤 식으로 사용하는지만 간단히 언급하도록 하겠습니다.


이전 시간까지 언급한 클래스 중에 NSFetchRequest는 'SQL문'에 해당한다고 앞서

말씀드렸습니다. 그러므로 where 조건절에 해당하는 predicate는 당연히 fetchRequest에

포함되어야 합니다. 다음과 같은 코드를 작성하시면 끝입니다.


다음은 이전 내용에서 보여드린 fetchedResultsController 메서드의 일부분입니다.


// Attribute의 이름을 설정

NSString *attributeName = @"sectionDate";

// predicate 생성

NSPredicate *predicate = [NSPredicate 

                   predicateWithFormat:@"%K LIKE[cd] %@"// where 조건문

                   attributeName,

                   [conditionString stringByAppendingString:@"*"]];

NSFetchRequest *fetchRequest = [[NSFetchRequest allocinit];


// fetchRequest에 predicate을 설정함

[fetchRequest setPredicate:predicate];

.

.

.


위 내용 중 눈여겨보실 것은 predicate를 생성하는 부분인데요. 그 중에서도 인자로 넘어가는

문자열을 구성하는 부분을 잘 보셔야 합니다. 먼저 위 predicate의 인자로 넘어가는 문자열을 SQL문으로

바꿔보면 다음과 같습니다.


where sectionDate like conditionString || '%'


만일 conditionString의 값이 '2010년 8월'이라면


where sectionDate like '2010년 8월%'


이렇게 되는 것입니다.


물론 이 밖에도 많은 표현이 가능하지만 앞서 말씀드린 대로 자세한 내용은 API 문서를 참고해주세요…^^;;;

해당 내용은 'Predicate Programming Guide'의 Predicate Format String Syntax절에 있습니다.



2. 테이블 뷰를 사용하지 않는 화면에서의 데이터 사용

(NSManagedObjectContext클래스의

                     executeFetchRequest메서드)


이전 시간에는 테이블 뷰를 통해 데이터를 화면에 보여주는 내용에 대해 살펴보았었습니다.

하지만 항상 테이블 뷰를 통해 테이터를 보여주기만 하는 것은 아니죠.


때론 UIView를 통해 데이터를 표현해 줄 수도 있습니다.


테이블 뷰를 통해 데이터를 표현하는 경우에는 section정보와 row정보등이 필요하며 이러한 경로를

통해 원하는 데이터를 불러오기 위해 indexPath라는 객체를 사용한다는 것을 말씀드린 바가 있습니다.

또한 데이터의 변경사항을 테이블 뷰에 반영하기 위해 NSFetchedResultsControllerDelegate에

선언된 4개의 메서드들이 필요하게 됩니다.


하지만 테이블 뷰가 아닌 곳에 데이터를 표현하는 경우는 위와 같은 정보들이 필요 없는 상황이

대부분이며 따라서 몇가지 구현은 생략을 하여도 됩니다.


즉, 단순히 필요한 여러건의 데이터만을 가져와 사용하는 경우에는 단지 NSFetchRequest

클래스만을 사용하여 처리 할 수 있게 되는 것입니다.


제가 만든 애플리케이션인  iPhotoDiary의 첫 화면은 등록된 아이들의 데이터를 가져와

그 중 한 아이의 신상 정보를 UIView 기반의 화면에 보여주는 것입니다.






제가 필요한 것은 단지 등록된 아이들의 데이터와 각각의 아이들을 선택할 경우 키워드가 될

아이들의 이름 뿐입니다. 섹션 정보도 필요 없고 또 이 화면에서는 데이터의 변경이 이루어지지도

않습니다. 따라서 직접 만든 다음의 메서드 하나에서 데이터의 사용이 완결됩니다.


- (void)fetchRequestResult:(NSString *)cName


인자로 넘어가는 것은 등록된 아이의 이름입니다. 어차피 많아야 3-4명의 아이들을 등록하여 사용하는

경우가 대부분일 것이라 판단했고 그렇다면 각각의 아이에 대해 코어 데이터에 접근하는 것은

낭비라고 생각하여 일단 이 함수에서 모든 데이터를 가져 온 후 데이터 건수만큼 아이들을 선택할 수

있는 버튼을 만들고 버튼을 클릭할 경우 이 함수에서 불러온 데이터 배열(ManagedObject의 배열)에서

이름을 비교하여 해당 데이터를 가져오도록 처리하였습니다.


따라서 이 함수에는 아주간단한 내용만으로 데이터를 가져오고 있습니다.

(그리고 사실상 인자로 넘어오는 cname이 필요가 없죠...^^;;;)

코드를 보시면


NSFetchRequest *fetchRequest = [[NSFetchRequest allocinit];

[fetchRequest setEntity:[NSEntityDescription 

  entityForName:@"ChildData" 

              inManagedObjectContext:managedObjectContext]];

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc

  initWithKey:@"birthday" ascending:YES];

NSArray *sortDescriptors = [[NSArray alloc]

  initWithObjects:&sortDescriptor count:1];

[fetchRequest setSortDescriptors:sortDescriptors];

NSError *error;

self.childDatas =

[managedObjectContext executeFetchRequest:fetchRequest error:&error];



[fetchRequest setSortDescriptors:sortDescriptors]; 이전까지의 내용은

잘 아실 것입니다. fetchRequest를 생성하고 대상 테이블을 ChildData로 지정하고 정렬 기준을

birthday로 하여 생일이 빠른 아이가 먼저 나오도록 하였습니다.


그리고 마지막으로 managedObjectContext의 executeFetchRequest 메서드를 통해 쿼리를 실행하고

이 메서드의 return값으로 데이터의 배열을 얻어오게 된 것입니다.

참고로 결과 값을 받은 self.childDatas는 NSArray객체입니다.


메서드의 이후 내용은 데이터의 값을 가지고 여러가지 가공을 하여 화면의 각 요소에 출력하는

내용입니다.


이와같이 데이터의 변경이 없는 단순 조회성 데이터의 사용은 managedObjectContext 객체만으로

충분합니다.



3. 정리


코어 데이터 사용의 끝자락이다보니 오늘은 좀 일찌감치 마무리를 합니다…^^;;;


초반에도 말씀을 드렸다시피 이 내용들은 전문적인 내용에 대한 설명이 아니라 초보 개발자의

시행착오를 그대로 보여드리는 것이 목적이기에 결과는 동일하지만 과정이 나쁜 그런 코드들이

많이 보이실 것으로 생각됩니다.


하지만 어느 정도 프로그래밍의 경험이 있으신 분 들은 과정이 얼마나 중요한지 잘 아실 것입니다.

그래서 이미 완성된 코드를 다시 Refactoring하기도 하는 것이죠.


안타깝게도 저는 Objective-C를 처음 접하는지라 어찌어찌 결과물은 만들었지만 잘못된 부분들을

수정하기는 쉽지가 않네요. 그나마 이 실전 소스 분석을 진행하면서 몇군데 어설픈 코드를 바로잡을

수는 있었습니다. 이런 부분에 대해서는 이 글을 보시는 많은 분들이 도움이 필요합니다…^^;;;


간단하게 코어 데이터를 구성하는 객체들을 다시 한 번 짚어보며 마무리를 하도록 하겠습니다.


ApplicationDelegate에서 한 번만 설정

1. Persistent store : 영구저장소 RDBMS의 데이터파일(.dbf파일 같은…)

2. Data Object Model : 데이터베이스의 전체적인 모양, Schema, ERD

3. Persistent store Coordinator : 영구 저장소에 접근하기 위한 객체

4. Managed Object Context : 객체 관리 컨텍스트. 코어 데이터를 사용하기 위한 핵심 객체


필요한 위치에서 수시로 사용되는 객체들

5. Entity Description : Entity 즉, 테이블과 관련된 정보를 갖고 있는 객체

6. Managed Object : 기본적으로 Entity의 구조(Schema)를 표현하나 쿼리의 결과로서 받아지는

                            데이터의 Row를 표현하기도 함

7. Fetch Request : 데이터를 불러오기 위한 쿼리를 표현하는 객체

8. Predicate : 쿼리문 중 where 조건절을 표현하는 객체

9. Fetched Results Controller : 코어 데이터로부터 가져온 데이터들을 계층적으로 관리하는 객체

                   테이블 뷰를 통한 데이터 표현에 사용된다.



4. Tip - Data Migration


코어 데이터로 작업을 하면서 가장 난감했던 상황이 .xcdatamodel 파일을 통해 Entity들과 각 Entity의

relationship 및 property들을 만들어놓고 나중에 이 것을 수정하여 빌드하고 실행시키면 데이터 모델이

서로 다르다고 에러를 토해내는 상황이었습니다.

즉, 수정된 데이터 모델을 적용시키는 방법을 몰랐던 것입니다.


그래서 많은 분들이 알고 계시겠지만 데이터 모델의 변경을 반영하는 방법을 팁으로 추가합니다.


우선 xcode 화면에서의 설정입니다.


1. xcode의 Groups & Files 창에서 .xcdatamodel 파일을 선택합니다.

2. xcode의 메뉴바에서 Design -> Data Model -> Add Model Version을 선택합니다.






이렇게 하고 Groups & Files 창에서 보시면 기존에 .xcdatamodel 파일이 하나 있던 것이

.xcdatamodeld라는 디렉토리로 바뀌어있고 그 디렉토리 아래 .xcdatamodel 파일이 존재할 것입니다.

현재 사용중인 데이터 모델은 파일 아이콘에 초록 바탕에 체크 표시가 된 그림이 추가됩니다.

그리고 이전 버전의 데이터 모델들은 파일명 끝에 일련 번호가 붙습니다. 이 때 번호가 클 수록

가장 오래된 버전입니다.


이런 과정을 거친 실제 데이터 모델 화면입니다.






다음은 ApplicationDelegate의 persistentStoreCoordinator 메서드에서 다음 코드를 추가합니다.


NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:

[NSNumber numberWithBool:YES], SMigratePersistentStoresAutomaticallyOption,

[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,

nil];


이 Dictionary객체는 persistentStoreCoordinator를 생성할 때 인자로 들어가게 됩니다.


전체 코드는 다음과 같습니다.


- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {

    if (persistentStoreCoordinator != nil) {

        return persistentStoreCoordinator;

    }

NSString *storePath =

[[self applicationDocumentsDirectory]

stringByAppendingPathComponent:@"NewIPhotoDiary.sqlite"];


NSURL *storeUrl = [NSURL fileURLWithPath:storePath];

NSDictionary *options =

[NSDictionary dictionaryWithObjectsAndKeys:

[NSNumber numberWithBool:YES], 

 NSMigratePersistentStoresAutomaticallyOption,

[NSNumber numberWithBool:YES],

 NSInferMappingModelAutomaticallyOptionnil];

    persistentStoreCoordinator =

[[NSPersistentStoreCoordinator alloc]

initWithManagedObjectModel: [selfmanagedObjectModel]];

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;

}


만일 managedObjectModel 메서드도 아래 코드와 다르다면 아래 코드처럼 바꿔주세요.


- (NSManagedObjectModel *)managedObjectModel {

    if (managedObjectModel != nil) {

        return managedObjectModel;

    }

NSString *path = [[NSBundle mainBundle]

pathForResource:@"NewIPhotoDiary"ofType:@"momd"];

NSURL *momURL = [NSURL fileURLWithPath:path];

managedObjectModel = [[NSManagedObjectModel alloc]

initWithContentsOfURL:momURL];

    return managedObjectModel;

}


이제 빌드하고 실행시키시면 잘 돌아갈 것입니다…^^



5. 마무리


처음으로 아이폰 앱을 만들 때만큼 좌충우돌, 시행착오의 연속이네요.

어렵사리 하나의 항목에 대한 글을 마쳤는데 부족한 부분이 너무 많이 느껴집니다.

이 글을 읽는 많은 분들의 양해를 바랄 뿐입니다…^^;;;


본격적으로 시작된 내용이 코어 데이터라서 더 많이 어려웠는지 모르겠습니다.

일단 이 부분에 대해서는 겸허하게 여러분들의 비판과 내용 수정을 기다리도록 하겠습니다.


이제 데이터를 처리하는 부분이 완성되었으니 다음시간부터는 iPhotoDiary의 화면을 순서대로

따라가면서 어떤 식으로 작업을 하였는지에 대해 설명을 드리도록 하겠습니다.

바로 다음 시간에는 첫 화면인 등록된 아이의 신상정보와 아이 선택 버튼이 있는 화면을

소스를 보면서 분석해보도록 하겠습니다.

앞으로는 그리 어려운 내용은 없을 것 같네요…^^


긴 글 읽어주셔서 감사하고 다음 시간에 뵙겠습니다.

블로그 이미지

마즈다

이제 반백이 되었지만 아직도 꿈을 좇고 있습니다. 그래서 그 꿈에 다가가기 위한 단편들을 하나 둘 씩 모아가고 있지요. 이 곳에 그 단편들이 모일 겁니다...^^

댓글을 달아 주세요