최초 작성일 : 2012/04/13 10:12 


일반적인 관계형 DBMS의 쿼리에서 where절의 역할을 하는 것이

코어 데이터의 NSPredicate 객체이다.

대체로 문법이 비슷하긴 하지만 아무래도 복잡한 쿼리를 구성하기에는 좀 답답한
감이 있다.

더구나 다음과 같은 케이스는 불필요한 코딩만 늘리는 것 같은데 이렇게 해야 한단다...-.-

NSPredicate를 이용한 like문은 대략 다음과 같다.
우선 일반적인 쿼리에서 like문에 사용하는 %는 CoreData는 *로 사용한다.

NSPredicate *predicate;

NSString *searchStr = [NSString stringWithFormat:@"*%@*", toMail];

predicate = [NSPredicate predicateWithFormat:@"%K LIKE[cd] %@ and %K LIKE[cd] %@",
attributeName, self.mailAddress, attributeName2, searchStr];

보시는 바와같이 그냥
predicate = [NSPredicate predicateWithFormat:@"%K LIKE[cd] %@ and %K LIKE[cd] *%@*",
attributeName, self.mailAddress, attributeName2, toMail];

이렇게 하면 될 것 같은데 위의 문장으로 해야만 검색이 정상적으로 이루어진다...-.-

*참고 :
LIKE[cd]에서 cd의 의미는 다음과 같다.

'cd' means case-insensitive and diacritic-insensitive.

즉, 대소문자 구분 안하고. 발음기호 구분 안한다는 의미이다.

블로그 이미지

마즈다

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

댓글을 달아 주세요

최초 작성일 : 2012/04/09 16:04 


현재 사용 중인 Xcode 버전이 4.3인데 바뀐 Xcode로 작업을 하려니

삽질이 이만 저만이 아니다.
대부분 아주 간단한 것인데 애플의 문서를 샅샅이 살펴보지 않는 한은
쉽게 발견하기 힘든 것들이기도 하다.

오늘은 Entity간의 Relationship에 대한 이야기다.

일단 Entity간의 구조는 이렇다.




보는대로 이메일과 첨부파일을 관리할 Entity이다.
당연히 EmailData와 AttachFiles의 관계는 1:N이다.

이 것이 Insert시에는 EmailData의 attachFiles에 NSSet 형태로 집어넣고

[emailData.managedObjectContext save:&error]만 해주면 첨부파일들이

자동으로 AttachFiles에 저장이 된다.


그런데 삭제시에는 요상하게 EmailData만 삭제가 되고 AttachFiles의 관련

데이터들은 삭제가 안될뿐더러 emailData 값이 NULL로 바뀌는 것이다.


이거 일단 emailData를 지우고 나중에 AttachFiles에서 emailData가 NULL인

놈들만 따로 지워야 하나...하고 고민하다가 결국엔 답을 찾았다.


아주 간단한 답을...-.-


일단 부모가 되는 EmailData의 Relationship인 attachFiles는

오른쪽 설정창에서 Delete Rule을 Cascade로 해준다.





그리고 자식에 해당하는 AttachFiles의 Relationship인 mailData는
Delete Rule을 Nullify로 해준다.




요렇게 하니 깔끔하게 처리된다~

블로그 이미지

마즈다

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

댓글을 달아 주세요

최초 작성일 : 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의 화면을 순서대로

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

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

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

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


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

블로그 이미지

마즈다

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

댓글을 달아 주세요

최초 작성일 : 2010/09/10 18:24 


0. 정정


지난 시간에 코어 데이터 관련 객체들 설명 중 ManagedObject에 대한 설명을 다음과 같이 하였습니다.


쉽게 말하면 Entity를 클래스 파일로 만들어놓은 것이라고

생각하시면 됩니다. 이후 실제 코딩 작업 설명에 자세히 말씀드리겠지만 xcdatamodel 파일을 통해

구성된 Entity들은 클래스 파일로 만들 수 있습니다.

다만 이 클래스 파일이 항상 필요한 것은 아니고 개발자가 별도의 메서드를 추가시키고자 할 때나

사용을 하게 됩니다.


한데 이 설명이 적절치 못한 것 같아 정정합니다.


우선 .xcdatamodel 파일을 통해 Entity를 실제 클래스 파일로 만들 수 있게 된다는 부분까지는

맞습니다. 그리고 이 클래스파일은 NSManagedObject 클래스를 상속받아 만들어지는데요.

하지만 이 이렇게 생성된 클래스 파일은 같은 이름의 Entity의 구조(스키마, schema)를 표현하긴

하나 실제로 인스턴스화 되어 사용될 때는 같은 이름의 Entity에 저장된 데이터 Row를 의미한다고

보셔야 할 것 같습니다. 이 내용은 아래 본문에 다시 언급하고 있습니다.


Tip Entity 클래스 파일 만드는 방법


1. 우선 .xcdatamodel을 선택하여 데이터 모델 설계 화면을 엽니다.

2. 좌측 상단의 Entity 목록 화면에서 Entity 하나를 선택합니다.




3. Groups & Files창의 필요한 위치에서 오른쪽 마우스 클릭 후 Add -> New File… 을 선택합니다.

4. New File 팝업창에서 iOS->Cocoa Touch Class를 선택하면 우측 화면에 Managed Object Class라는

   새로운 항목이 보입니다. 이것을 선택합니다.




5. 진행하다보면 현재까지 만든 모든 Entity목록을 보여주는 화면이 보이고 이 화면에서 클래스를 만들

   Entity를 선택해서 클래스 파일을 만들면 됩니다.





1. 복습


먼저 지난 시간 분석한 내용을 요점 정리 해보면 다음 4 개의 메서드로 집약될 것입니다.


managedObjectContext

persistentStoreCoordinator

managedObjectModel

applicationDocumentsDirectory


이 메서드들 중에서도 가장 중요하다고 할 수 있는 것은  managedObjectContext입니다.

물론 나머지 3개의 메서드들도 당연히 중요하지만 일단 자주 등장하고 자주 사용하게 된다는

면에서 managedObjectContext가 중요한 것입니다. 간단하게 그 흐름을 되집어보면


1. managedObjectContext의 인스턴스 를 구하기 위해 같은 이름의 메서드를 호출합니다.

2. managedObjectContext를 구하기 위해서는 영구 저장소에 접근해야 하기 때문에 영구

   저장소에 접근하기 위해 사용되는 persistentStoreCoordinator의 인스턴스를 얻게됩니다.

3. persistentStoreCoordinator를 얻기 위해 필요한 정보인 영구 저장소가 존재하는 경로와

   영구 저장소에 저장되어있는 데이터의 논리적인 구현을 표현하는 managedObjectModel

   을 구하게 됩니다.


   이를 위해서 applicationDocumentsDirectory와 managedObjectModel 메서드가 호출됩니다.


이런 과정을 거쳐 얻게 되는 managedObjectContext에는 우리가 사용하게 될 Entity(테이블)와

property(필드 또는 컬럼), 그리고 relationship(Entity간의 관계)에 대한 정보들이 들어있습니다.

그래서 본격적인 데이터 관련 작업은 managedObjectContext에서 시작된다고 보시면 됩니다.


위의 4가지 메서드는 프로젝트 생성시 'Use Core Data for storage'옵션을 체크하면 자동으로

코드가 생성된다는 것도 지난시간에 말씀드렸습니다. 그리고 특별한 이유가 없는한 이렇게

자동 생성된 4개의 메서드는 전혀 수정하실 필요도 없이 그냥 사용하면 됩니다.


2. managedObjectContext로부터 시작하기


지난 시간에 언급한 내용중에 제 실수를 하나 말씀드렸습니다.

그것은 managedObjectContext를 사용하는데 managedObjectContext가 필요한 뷰 컨트롤러에서

ApplicationDelegate로부터 managedObjectContext를 가져오는 것이 아니라 ApplicationDelegate에서

각각의 뷰 컨트롤러에 managedObjectContext를 할당해 주었던 것입니다. 이렇게 될 경우 아무래도

managedObjectContext를 사용하지 않는 시점에도 managedObjectContext 객체의 retain count가

올라간다든지 하는 등의 문제가 발생할 소지가 있을 것입니다.


즉, 특정 뷰 컨트롤러에서 managedObjectContext가 필요하다면 viewDidLoad 정도의 위치에서

ApplicationDelegate에 접근하여 managedObjectContext를 얻어와야 한다는 말입니다.

다음 코드처럼 말이죠.


iPhotoDiaryAppDelegate *appDelegate =

     (iPhotoDiaryAppDelegate *)[[UIApplication sharedApplication] delegate];

self.managedObjectContext = appDelegate.managedObjectContext;


이제부터 이 뷰 컨트롤러에서는 managedObjectContext의 인스턴스를 사용할 수 있게 된 것입니다.


그럼 이 managedObjectContext를 이용해서 어떤 작업을 하게되느냐…


우선 잠시 말을 돌려서 일반적인 RDBMS에서의 작업 과정을 살펴보도록 하죠.

우선 DBMS에 Connection을 해야 합니다. 그리고나서는 적절한 쿼리를 DBMS로 전송을 하게되고

DBMS에서는 쿼리를 파싱한 후 쿼리가 요청하는 내용에 대해 Resultset을 돌려주게 됩니다.

이후에는 개발자가 Resultset을 가지고 적절히 화면을 구성하여 데이터를 화면에 뿌려주게 되는

것이죠.


이 과정을 코어 데이터에 적용을 시켜보면

우선 managedObjectContext를 DBMS에 대한 연결 정보라고 생각하셔도 될 것입니다.

이 managedObjectContext의 인스턴스를 얻었다는 것은 DB에 연결하여 데이터를 가져올 준비가

되었다는 의미입니다.


그러면 이제 쿼리를 전송하고 쿼리에서 요청한 결과셋을 가지고 와야 하는데요.

이러한 작업을 수행하는 객체가 바로 NSFetchedResultsController클래스의 객체입니다.

NSFetchedResultsController클래스의 인스턴스를 통해 다음의 메서드를 수행함으로써

영구 저장소로부터 필요한 데이터들의 결과값을 가져오게 됩니다.


- (BOOL)performFetch:(NSError **)error


실제 사용 예는 다음과 같습니다.


NSError *error;

if (![[self fetchedResultsController] performFetch:&error]) {

// Update to handle the error appropriately.

NSLog(@"Unresolved error %@, %@", error, [error userInfo]);

exit(-1);  // Fail

}


여기서 fetchedResultsController인스턴스는 fetchedResultsController라는 메서드를 통해서

얻게 되는데요. 이 때 fetchedResultsController 서드 내에서 managedObjectContext가 인자로

쓰이게 됩니다.


fetchedResultsController의 내용은 다음과 같습니다.


- (NSFetchedResultsController *)fetchedResultsController {

    if (fetchedResultsController != nil) {

        return fetchedResultsController;

    }

    

// Create and configure a fetch request with the Book entity.

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

NSEntityDescription *entity =

            [NSEntityDescription entityForName:@"DiaryData" 

                                 inManagedObjectContext:managedObjectContext];

[fetchRequest setEntity:entity];

// Create the sort descriptors array.

NSSortDescriptor *birthdayDescriptor =

            [[NSSortDescriptor alloc] initWithKey:@"writedate" ascending:YES];

NSArray *sortDescriptors =

            [[NSArray alloc] initWithObjects:birthdayDescriptor, nil];

[fetchRequest setSortDescriptors:sortDescriptors];

// Create and initialize the fetch results controller.

NSFetchedResultsController *aFetchedResultsController =

         [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest  

          managedObjectContext:managedObjectContext sectionNameKeyPath:nil  

          cacheName:@"Root"];

self.fetchedResultsController = aFetchedResultsController;

fetchedResultsController.delegate = self;

// Memory management.

[aFetchedResultsController release];

[fetchRequest release];

[birthdayDescriptor release];

[sortDescriptors release];

return fetchedResultsController;

}


한 줄 한 줄 분석을 해보면 먼저 fetchedResultsController도 지연로딩을 사용하고 있습니다.

fetchedResultsController 인스턴스가 존재하면 기존 인스턴스를 반환하고 그렇지 않으면

새로 생성을 하여 반환합니다.


if (fetchedResultsController != nil) {

return fetchedResultsController;

}


다음에는 쿼리문을 준비하는 과정입니다.


NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

NSEntityDescription *entity =

            [NSEntityDescription entityForName:@"DiaryData" 

                                 inManagedObjectContext:managedObjectContext];

[fetchRequest setEntity:entity];


fetchRequest 인스턴스를 하나 만들고 managedObjectContext내에 있는 EntityDescription으로부터

'ChildData'라는 이름의 Entity(테이블)를 가져와서 쿼리에 사용하겠다고 설정하는 것입니다.

즉, 'ChildData'라는 테이블을 쿼리의 대상으로 하겠다고 선언한 것입니다.


※ 주의할 것은 이 때의 ChildData는 테이블을 의미하고 있지만 나중에 나오게될 ChildData의 인스턴스는

ChildData라는 테이블에 저장되어있는 Row에 해당한다고 보셔야 한다는 것입니다. 이 내용은 이후 다시 한 번

반복하도록 하겠습니다.


다음의 내용은 SQL문의 order by절에 해당한다고 보시면 됩니다.

정렬 기준 property(컬럼)와 정렬 방법(오름차순 혹은 내림차순)을 지정하게 됩니다.


NSSortDescriptor *birthdayDescriptor =

            [[NSSortDescriptor alloc] initWithKey:@"writedate" ascending:YES];

NSArray *sortDescriptors =

            [[NSArray alloc] initWithObjects:birthdayDescriptor, nil];

[fetchRequest setSortDescriptors:sortDescriptors];


두 번째 줄을 보시고 짐작들 하셨겠지만 NSSortDescriptor는 여러개를 지정할 수 있습니다.

NSSortDescriptor의 배열이 최종적으로 fetchRequest에 지정되는 것이죠.


여기까지 해서 쿼리(fetchRequest)에는 2가지 정보가 설정되었습니다.

데이터를 불러올 대상 테이블과 데이터를 불러올 때의 정렬에 대한 정보입니다.


마지막으로 이렇게 설정된 쿼리문을 가지고 managedObjectCContext로부터 Resultset을 가져오게 되는

문장이 바로 다음 내용입니다.


NSFetchedResultsController *aFetchedResultsController =

         [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest  

          managedObjectContext:managedObjectContext sectionNameKeyPath:nil  

          cacheName:@"Root"];

self.fetchedResultsController = aFetchedResultsController;

fetchedResultsController.delegate = self;


세번째 인자는 테이블 뷰를 사용하게 되는 경우 섹션 이름으로 사용하게 될 property(컬럼)를 지정하는 것이고

마지막 인자는 캐시로 사용할 파일 이름입니다.


여기까지 하면 일단 기본 기능은 처리가 됩니다. 해당 Entity의 모든 데이터들을 NSSortDescriptor들의

기준에 따라 불러오게 되는 것이죠.


하지만 이 것만으로는 당연히 부족합니다. 단지 1건의 데이터를 사용하기 위해서 100건 1000건의 데이터를

불러와야 한다면 엄청난 낭비겠죠. 물론 100건 1000건 불러온다고 해서 엄청난 부하가 걸리거나 하진

않겠지만 말이죠…^^;;; 물론 이를 해결할 옵션 메서드들이 있지만 말입니다. 자세한 내용은 API 문서를

참고하세요.


어찌되었건 여기까지 정상적으로 데이터를 불러와서 이제 사용하기만 하면 되는 시점에 왔네요.


3. TableViewController에서 사용하기


UITableViewController는 UITableViewDataSource 프로토콜을 구현하고 있는데요.

여기에는 테이블 뷰 이용시 필수라고 할 수 있는 몇가지 메서드가 구현되어야 합니다. 다음과 같은 메서드들이죠.


- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section


프로토콜 이름에서도 알 수 있듯이 이 메서드들은 모드 데이터 소스와 관련된 내용이고 따라서 필요한

중요 정보들을 코어 데이터 처리를 통해 가져온 fetchedResultsController 인스턴스를 통해 가져오게 됩니다.

주요 사용 정보는 section 정보입니다. 물론 fetchedResultsController 생성시  sectionNameKeyPath:nil

코딩한 경우에는 섹션이 나누어지지 않기 때문에 섹션 수는 1로 나오겠죠.


섹션 수를 가져오는 코딩입니다.


- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {

NSInteger count = [[fetchedResultsController sections] count];

.

.

.

}


리고 각 섹션에 포함된 데이터 수를 가져오는 코딩입니다.


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

NSInteger numberOfRows = 0;

if ([[fetchedResultsController sections] count] > 0) {

id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];

     numberOfRows = [sectionInfo numberOfObjects];

}

.

.

.

}


다음으로 사용하는 정보는 특정 인덱스에 있는 실 데이터의 클래스(ManagedObject 타입의 클래스)를

가져오게 됩니다.


데이터 클래스를 가져오는 코딩입니다.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

diaryData =

(DiaryData *)[fetchedResultsControllerobjectAtIndexPath:indexPath];

.

.

.

}


마지막으로 섹션 이름을 가져오는 코딩입니다. 섹션이름은 fetchedResultsController 생성시 sectionNameKeyPath:에 넣어준 값입니다.

물론 sectionNameKeyPath:nil로 한 경우 이 값은 없죠.


- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {

return [[[fetchedResultsController sections] objectAtIndex:section] name];

}



※ 참고로 이 때 사용되는 인덱스 정보는 index가 아니라 indexPath입니다. 이름에서도 알 수 있듯이 특정 위치의

인덱스 값 하나만을 의미하는 것이 아니라 여러층으로 중복된 배열 구조의 인덱스 경로를 모두 표시하는

객체라고 볼 수 있겠죠. 이 것을 통해 fetchedResultsController의 구조나 fetchedResultsController로부터

실 데이터를 불러오는 과정을 유추해볼 수 있습니다.


즉, fetchedResultsController는 중첩된 배열 구조로 section이라는 배열이  있고 이 배열은 그 요소로

데이터  Row의 배열을 가지고 있는 2차원 배열 구조로 보시면 된다는 것입니다. 아래 그림과 같겠죠.






만일 'Row12'라는 데이터를 찾아야 한다면 우선 section 인덱스인 2를 찾아야 하고 다음 데이터의 인덱스인

1을 찾아가야 해서 2 -> 1의 경로를 가져야 합니다. 바로 이러한 인덱스의 경로를 indexPath가 표현하고

있는 것이죠.


두말할 것 없이 자세한 내용은 API 문서를 참고하세요…^^;;;




※ 앞서도 언급했지만 Entity와 데이터 Row를 잘 구분하셔야 합니다.

일단 .xcdatamodel을 통해 생성된 Entity들은 기본적으로 NSManagedObject타입의 클래스입니다.

위의 예제 코드에서는 'DiaryData'라는 Entity에서 데이터를 가져온 것입니다. 하지만 실제로


(DiaryData *)[fetchedResultsController objectAtIndexPath:indexPath];


이 코드를 통해 가져온 DiaryData 클래스의 인스턴스는 Entity를 표현한 것이 아니라 Entity

저장된 Data Row, 즉, 한 건 한 건의 데이터를 의미한다고 보시는 것이 옳을 것입니다.



여기까지 진행되었다면 일단 화면상의 테이블 뷰에 데이터 리스를 뿌리는 것 까지는 문제없이 실행될 것입니다.


4. 요약


오늘도 쓸데없이 말만 길어졌고 내용은 여전히 혼란스럽네요.

그렇기 때문에 저 역시 여러분의 도움이 많이 필요합니다…^^;;; 제가 틀렸거나 잘못 이해하고 있는 부분은

언제든지 말씀을 해주세요.


일단 지난 시간에 managedObjectContext까지 만들었고 오늘은 이 managedObjectContext를 이용하여 실제 데이터를

가지고 있다고 볼 수 있는 fetchedResultsController를 생성한 후 테이블 뷰 컨트롤러를 이용하는 화면에서 이

fetchedResultsController를 이용하여 UITableViewController(UITableViewDataSource 프로토콜)의 몇몇 메서드를

이용하여 section정보와 실 데이터 row의 정보를 가져오는 방법을 알아보았습니다.


하지만 아직 predicate는 사용하지 않았네요…^^;;;


5. 마무리


여기까지 일단 코어 데이터의 여러 객체들을 이용하여 테이블 뷰에 데이터를 출력하는 내용까지는

정리를 해보았습니다. 하지만 꼭 데이터를 테이블 뷰에만 출력하는 것은 아니죠.

그래서 다음시간에는 FetchedRequest 객체와 Predicate 객체를 이용하여 데이터 한 건만 불러와 화면에

보여주는 내용으로 Core Data에 관한 내용을 마무리하도록 하겠습니다.

블로그 이미지

마즈다

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

댓글을 달아 주세요

최초 작성일 : 2010/09/03 01:47 


우선 먼저 양해 말씀 드릴 것은 제가 이 실전 소스 분석을 진행하는 목적은

애플이 제공하는 API에 대한 자세한 설명이 아니라 이제 시작하는 개발자로서

일종의 시행착오 경험을 공유하고자 하는데 있습니다. 따라서 기본 API에 대한

설명은 과감하게 생략을 하고 넘어갑니다. 이점 양해 부탁드립니다.


==============================================


1. 아이폰에서의 데이터 관리


이번 분석에서는 일반적인 진도상으로는 좀 이른 감이 있지만 소스의 흐름상 먼저 등장을 하고 있으므로

코어 데이터에 대해 다뤄보도록 하겠습니다.


잘 알고들 계시듯이 아이폰에서는 여러가지 방식의 데이터 저장 공간을 사용할 수 있습니다.

SQLite를 직접 사용하는 방법, 바이너리 파일을 사용하는 방법

(여기에는 다시 property list를 사용하는 방법과 객체 archiving을 이용하는 방법 2가지가 있습니다.),

그리고 마지막으로 코어 데이터를 이용하는 방법.


각각의 특징을 보자면 다음과 같습니다.


1.1 SQLite

우선 가장 접근하기 쉬운 것은 SQLite를 직접 사용하는 방법이라고 생각됩니다.

기존에 웹이나 기타 RDBMS 관련 개발을 해보신 분들은 쿼리를 직접 작성하여 entity간의 관계를 지정하고

검색 조건을 위한 where절을 만들고 하면서 익숙하게 데이터 처리를 할 수 있을 것입니다.


1.2 Binary

다음으로 바이너리를 이용한 방법은 기존 언어에서 객체 직렬화라는 부분을 생각하시면 될 것입니다.

객체 직렬화(serialization)란 데이터를 저장하고 있는 클래스의 인스턴스를 그대로 바이너리 파일로

저장하는 기법입니다. JAVA의 예를 들면 HashMap을 상속받은 클래스에 나라 이름을 key로 하고

그 나라 수도를 value로 하여 저장을 한 뒤 이 클래스의 인스턴스를 파일로 저장시키는 방법입니다.


괜히 말이 복잡해졌는데 쉽게 말씀드려 텍스트 에디터로 열었을 때 이상한 문자로 보여지는 파일이

특정 애플리케이션에서는 정상적인 문자로 보여지는 파일들은 모두 이러한 객체 직렬화를 통해

저장되었다고 보시면 됩니다…^^


예를 들어 아래 한글의 hwp 파일은 텍스트 에디터에서는 이상한 문자로 보이지만 아래한글 프로그램에서는

정상적인 문서로 보이는 것이 그런 경우입니다.


1.3 Core Data

마지막으로 애플에서 구현해놓은 Core Data를 이용하는 방법입니다.

사실 제가 이 방식을 선택한 이유는 애플에서 구현해 놓은 것이기 때문에 반드시 익혀놓아야 할

필요성이 있을 것 같아서였는데 익숙하지 않은 구조로 인해 엄청 애먹었습니다…ㅠ.ㅠ

일종의 ORMapping 역할을 하는 API입니다. JAVA로 보자면 iBatis나 Hibernate에 해당하는 건데요.

원칙적으로 따지자면 개발자는 무지 편한 구조입니다. 일일이 쿼리를 작성하지 않아도

(물론 iBatis나 Hibernate에서는 최초 쿼리는 작성을 해 놓아야 합니다. 단지 재사용이 가능할 뿐이죠)

데이터를 관리할 수 있다는 것이죠.


필요한 메서드에 필요한 인자만을 넘겨서 호출하면 Core Data가 다 알아서 쿼리도 날려주고

결과 값도 돌려주고 한다는 것이죠. 하지만 이 캡슐화(객체 지향의 주용 개념 중 하나로 핵심 로직을

인터페이스 뒤로 숨기고 개발자는 외부로 드러난 인터페이스만을 통해 API를 다룰 수 있게 하는 것)는

저처럼 실력이 떨어지는 개발자에게는 무지 헷갈리는겁니다. 자꾸 몰라도 되는 부분이 궁금해져서 진도가

안나가는거죠…-.-


어쨌든 익숙해지면 가장 편한 방법이 되겠지만 세밀한 컨트롤을 원하는 개발자들에게는

다소 답답한 면이 있을 것입니다.


사실 아직도 제가 잘 모르겠는 부분은 Entity간의 relationship을 어떻게 사용하느냐 하는 것입니다.

기존 RDBMS같은 경우 쿼리를 통해 다양한 join을 만들어 데이터를 불러오는데 코어 데이터에서는

어떤 부분이 그런 기능을 하게 되는지 잘 모르겠습니다. 이 부분. 즉, 테이블간의 조인이

코어 데이터에서는 어떤 식으로 이루어지고 또 개발자들은 어떤 작업을 해주어야 하는지에

대해 아시는 분은 부연 설명 부탁드립니다...^^;;;


2. Core Data의 구조


시작이 너무 장황했네요.

암튼 결론적으로 저는 Core Data를 선택했고 생소한 개념과 객체들로 인해 상당히 헤맸고

아직도 헤매는 중입니다…^^;;;


사실 이글을 작성하는 시점에서도 이 부분을 어떻게 풀어나가야 하는 고민에 손을 못대고 있었는데

다행히 한눈에 딱 들어오는 개념도가 보였습니다.


아래 그림은 '위키북스'에서 나온 'More 아이폰 3 프로그래밍'이라는 책에 실린 그림입니다.






책에는 한글로 표시되어 있었는데 오히려 더 헷갈리는 것 같아서 제가 API용어로 다 바꿔봤습니다.

각각의 의미와 역할은 다음과 같습니다.


1. Persistent store : 영구 저장소라고 해석되고 가장 최종 데이터가 저장되는 영역입니다.

                          물리적으로는 하드디스크(메모리)에 저장된 데이터를 가지고 있는

                          바이너리 파일이라고 생각하시면 됩니다.

2. Data Object Model : Persistent store에 저장된 내용들을 논리적으로 보여주는 객체라고 생각하시면 됩니다.

                          xcode상에 보여지는 xcdatamodel 파일을 생각하시면 될 것 같습니다.

3. Persistent store Coordinator : 영구 저장소와 Managed Object Context를 이어주는 다리 역할을 한다고

                          보시면 됩니다. 항상 영구 저장소에서 뭔가 실제적인 것을 가지고 오려고 하면 직접

                          영구 저장소에 접근하는 것이 아니라 이 Persistent store Coordinator를 거쳐야 하는 것입니다.

                          '왜 그렇게 해야 하는가?'라는 질문은 잡스형님께…^^;;;

                          (이런 것도 사실 객체 지향의 일환은로 핵심적인 내용들은 보다 안정적인 위치에 감춰두고

                           개발자들에게는 제한된 인터페이스만을 제공하여 불필요한 변경으로 인한 문제를 줄이는 동시에

                           내부 API가 변경이 되더라도 개발자들은 딱히 수정을 하지 않아도 되도록 하는데 그 목적이 있겠죠.)

4. Managed Object Context : 객체 관리 컨텍스트인데요 개발자의 작업은 여기서부터 시작된다고 보셔도 됩니다.

                          위의 3가지는  최초 애플리케이션이 실행되는 시점에 한 번 코딩을 해주시면 되고 

                          이 Managed Object Context부터는 수시로 사용을 하게 됩니다.

                          물론 인스턴스를 계속 만드는 것은 아니구요.

                          자세한 것은 코딩을 보시면서…^^

5. Entity Description : Entity는 쉽게말해 RDBMS의 테이블을 생각하시면 됩니다. 특정 Entity와 관련된 각종 정보를

                          가져올 수 있는데요 Managed Object Model이나 property(RDBMS의 필드 혹은

                         컬럼이라고 생각하시면 됩니다.)

                          또는 타 Entity와의 관계(relationship) 등의 정보를 가져오거나 설정할 수 있습니다.

6. Managed Object : Managed Object Context에서 관리되는 객체 중 하나입니다.

                          쉽게 말하면 Entity를 클래스 파일로 만들어놓은 것이라고

                          생각하시면 됩니다. 이후 실제 코딩 작업 설명에 자세히 말씀드리겠지만 xcdatamodel 파일을 통해

                          구성된 Entity들은 클래스 파일로 만들 수 있습니다.

                          다만 이 클래스 파일이 항상 필요한 것은 아니고 개발자가 별도의 메서드를 추가시키고자 할 때나

                          사용을 하게 됩니다.

                          전 아무생각없이 다 만들었네요…-.-

7. Fetch Request : 속칭 '쿼리를 날린다'라는 것입니다…^^ 실제 이 과정을 통해 결과값을 받아오게 되죠.

8. Predicate : '술어'라고 해석을 하더군요. where 조건에 해당하는 객체입니다.

                  이런 부분의 사용이 아이폰 개발을 처음 접하는 개발자들을 울리게 되죠…ㅠ.ㅠ


3. 실제 소스 보기


우선 디버깅을 위해 찍은 로그를 좀 보시죠.

최초 실행되는 iPhotoDiaryAppDelegate에서 단순히 실행되는 순서대로 메서드명을 찍은 것입니다.


2010-09-02 22:43:14.742 iPhotoDiary[2610:307] DEBUG: applicationDidFinishLaunching

2010-09-02 22:43:14.753 iPhotoDiary[2610:307] DEBUG: managedObjectContext

2010-09-02 22:43:14.759 iPhotoDiary[2610:307] DEBUG: persistentStoreCoordinator

2010-09-02 22:43:14.764 iPhotoDiary[2610:307] DEBUG: applicationDocumentsDirectory

2010-09-02 22:43:14.778 iPhotoDiary[2610:307] DEBUG: managedObjectModel

2010-09-02 22:43:14.925 iPhotoDiary[2610:307] DEBUG: managedObjectContext

2010-09-02 22:43:14.931 iPhotoDiary[2610:307] DEBUG: managedObjectContext

2010-09-02 22:43:14.936 iPhotoDiary[2610:307] DEBUG: managedObjectContext

2010-09-02 22:43:14.941 iPhotoDiary[2610:307] DEBUG: applicationDocumentsDirectory


이제는 익숙해진 단어들이 보이네요.


다음은 소스입니다.


- (void)applicationDidFinishLaunching:(UIApplication *)application {

Debug(@"applicationDidFinishLaunching");

// 이 부분에서 앞으로 데이터를 사용할 필요가 있는 컨트롤러들에게 ManagedObjectContext들을 할당해주고 있습니다.

//    처음에는 Core Data의 구조를 몰라서 이러한 순서를 통해 ManagedObjectContext를 가져온 것이 아니라

//    각 화면 컨트롤러에서 별도로 ManagedObjectContext의 인스턴스를 만들어서 사용했더니 계속 Entity를

//    찾을 수 없다는 오류가 뜨더군요.

appMainViewController.managedObjectContext = [self managedObjectContext];

calendarView.managedObjectContext = [self managedObjectContext];

diaryListController.managedObjectContext = [self managedObjectContext];

eventListController.managedObjectContext = [self managedObjectContext];

// 요거는 다음번에 설명을 하겠지만 binary형태의 저장중 property list를 이용한 저장 관련 내용입니다.

[self writeToPlist];


// 다 아시는 내용…^^;;;

[window addSubview:tabBarController.view];

[window makeKeyAndVisible];

}



위 코드는 이 글을 작성하면서 바뀌었습니다. 역시 무식하면 손발이 고생이라고…ㅠ.ㅠ

원래의 코드는 탭바에서 보여질 각 화면 컨트롤러에 managedObjectContext를 넘겨주는 방식인데

사실 코딩을 하면서도 뭔가 이상하다 했습니다…^^;;;


ApplicationDelegate에 접근하는 방법을 몰랐던 거죠.


결국 각각의 화면 컨트롤러의 fetchedResultsController를 호출하는 시점 이전에

       

iPhotoDiaryAppDelegate *appDelegate =

   (iPhotoDiaryAppDelegate *)[[UIApplication sharedApplication] delegate];

self.managedObjectContext = appDelegate.managedObjectContext;


이렇게 코딩하여 변경하였습니다.


호출 순서대로 설명을 해보자면


최초에


appMainViewController.managedObjectContext = [self managedObjectContext];

호출을 하게되면 영구 저장소에 접근하기 위해서 persistentStoreCoordinator를 호출하게 되고

persistentStoreCoordinator는 영구 저장소가 위치한 경로를 알아내기 위해

applicationDocumentsDirectory를 호출하게 되는 것입니다.


이후로 3번 더 managedObjectContext를 호출하였는데 더이상 persistentStoreCoordinator

applicationDocumentsDirectory가 호출되지 않은 이유는 managedObjectContext

persistentStoreCoordinator가 지연 로딩. 즉, 이미 해당 객체가 있으면 기존의 객체를 리턴하고

없으면 새로 생성해서 넘겨주는 것이죠. 그렇기 때문에 최초 managedObjectContext호출 후 객체가

생성되었으므로 이후 3번의 호출에는 managedObjectContext객체를 새로 생성하는 것이 아니라

기존 객체만을 돌려주어 이후 메서드들이 호출되지 않은 것입니다.


나머지 메서드들은 최초 프로젝트 생성시 코어데이터를 사용하겠다는 옵션에 체크를 하셨다면 자동으로 생성되는

메서드들이라 코드는 생략합니다.


프로젝트 생성시 네비게이션 베이스, (아이패드)스플릿 뷰 베이스, 유틸리티 어플리케이션, 윈도우 베이스 등의

프로젝트에는 User Core Data for storage라는 옵션이 나옵니다.






물론 프로젝트 생성시 이 옵션을 체크하지 않았어도 데이터 모델을 만들 수 있습니다.

Groups & Files에서 적절한 위치에 오른쪽 버튼 클릭 후 Add -> New File…을 선택하시면 iPhone OS의 Resource 항목에

Data Model이 있습니다. 물론 이렇게 만들었을 경우에는 아래 4개의 메서드들을 모두 코딩을 해주어야 합니다.


managedObjectContext

persistentStoreCoordinator

managedObjectModel

applicationDocumentsDirectory






4. 정리


우선 최초 말씀드린대로 각각의 API 함수들에 대한 내용은 제 설명보다는 다른 강좌나 애플의 API 문서를 보시는 것이

이해가 빠를 것입니다. 그래서 자세한 설명은 생략을 하였습니다.


다만 중요한 것은 Core Data의 구조와 흐름이 어떻게 되느냐입니다.


Persistent store에 applicationDocumentDirectory로 영구 저장소의 경로를 확인한 Persistent store Coordinator를 통해 접근하여

ManagedObjectContext를 가져오고 개발자들은 ManagedObjectContext를 이용하여 구체적인 작업을 하면 된다.


5. 마무리


아직 초반인데 너무 깊이 들어간 면이 없지 않습니다.

하지만 제가 제목을 [실전 소스 분석]이라 한 만큼 이미 완성된 소스를 따라가면서 전체적인 프로그래밍의 흐름을

되짚어보고자 하는 것이 목적이다보니 이른 시점에 어려운 내용이 등장을 하게 되었네요.


내용이 내용이니만큼 한 회 분량으로는 도입부 정도의 내용밖에는 다루지를 못했습니다.

다음 시간에 나머지 내용들, ManagedObjectContext, FetchResultController, Predicate 등을 다루어 실제

데이터를 읽고, 쓰고, 지우고, 수정하는 부분을 살펴보도록 하겠습니다.


벌써 9월이네요. 환절기에 감기들 조심하시고

다음 시간에 뵙겠습니다.

길고 영양가 없는 글 읽어주셔서 감사합니다.

블로그 이미지

마즈다

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

댓글을 달아 주세요