[iOS/OSX] CoreData #3 NSFetchedResultsController
CoreData 에서는 NSFetchedResultsController 라는 컨트롤러가 제공된다. UI 툴킷에서만 붙던 컨트롤러라는 이름이 약간 어색하긴 한데 상당히 편리하게 써 먹을 수 있는 기능이 제공된다.
이 포스트의 예제는 좀 축약된 형태라서 부족할지도 모르겠지만 참고사항이 될 수 있으면 좋겠다.
차이점으로 FetchBatchSize 같은 항목도 보이고 cacheName 도 보이고 이전의 fetch 할 때의 모양과는 좀 다르다. 그리고 delegate 도 보인다.
추신) 위의 코드에선 생략되어 있지만 frc 라고 생성한 오브젝트는 잘(?) 보관해 두는 식으로 구현하면 편하다.
읽어들인 전체 레코드의 갯수는 아래와 같은 식으로 읽을 수 있다.
하지만 이것 뿐 만이 아니다. delegate와 관련된 내용이 어떻게 보면 핵심이다.
이 프로토콜은 컨트롤러가 담당하고 있는 엔티티(테이블)의 내용이 변화 되었음을 알려주기 위한 용도이다.
CoreData 와 관련된 첫 번째와 두 번째 포스팅에서 삽입, 삭제, 수정에 대한 것을 이야기 했었다. 만약 이런 삽입이나 수정, 삭제 같은 동작이 발생하면 위의 delegate 에 위임받은 셀렉터가 호출된다.
예를 들어 삽입(INSERT)이 일어나면 controller:didChangeObject... 셀렉터가 아래와 같은 정보로 호출된다:
NSFetchedResultsController 의 가치를 가장 잘 보여주는 예가 바로 이 UITableView 와의 연계이다. 데이터베이스에 레코드가 바로 추가되면 delegate 를 이용해 테이블뷰의 내용을 쉽게 갱신 할 수 있도록 구조화 되어있다. 물론 수정이나 삭제 시에도 마찬가지이다. NSIndexPath 를 그대로 사용하므로써 데이터소스와의 연계도 쉽게 가능하다.
만약 네트워크로 내용을 가져와서 표시하는 앱이라면 아래와 같은 식으로 로직을 분리 할 수 있다.
만약 이런 기능이 없었다면 데이터베이스와 네트워크 등의 로직을 연결해서 순차적으로 동작하게 해야만 한다. 프로그래머로썬 상당히 골치아픈 일이다.
추신) section 에 관해서는 생략했다. 필요하다면 어떻게 쓰는지 찾아보자.
관련포스트:
이 포스트의 예제는 좀 축약된 형태라서 부족할지도 모르겠지만 참고사항이 될 수 있으면 좋겠다.
NSFetchedResultsController
아래의 예제는 NSFetchedResultsController 를 생성하는 예제로 역시 이전 포스팅의 내용이 그대로 이어진다.NSFetchRequest *fetch = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"TestEntity" inManagedObjectContext:self.context]; [fetch setEntity:entity]; NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"when" ascending:YES]; [fetch setSortDescriptors:[NSArray arrayWithObject:sort]]; [fetch setFetchBatchSize:25]; NSFetchedResultsController *frc = [[NSFetchedResultsController alloc] initWithFetchRequest:fetch managedObjectContext:self.context sectionNameKeyPath:nil cacheName:@"CACHENAME"]; NSError *error = nil; [frc performFetch:&error]; if (error) { NSLog(@"Failed to perform fetch"); return; } frc.delegate = self;우선 NSFetchRequest 에 NSSortDescriptor 가 이용된다. 이는 앞서 이야기 한 정렬 기능을 적용한 것이다.
차이점으로 FetchBatchSize 같은 항목도 보이고 cacheName 도 보이고 이전의 fetch 할 때의 모양과는 좀 다르다. 그리고 delegate 도 보인다.
추신) 위의 코드에선 생략되어 있지만 frc 라고 생성한 오브젝트는 잘(?) 보관해 두는 식으로 구현하면 편하다.
레코드를 읽기 및 갯수 알아내기
NSIndexPath 를 이용해 특정 위치의 레코드를 마음껏 읽을 수 있다. 예를 들어 첫 번째 row 의 레코드를 읽을 때는NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection0]; TestEntity *e = [frc objectAtIndexPath:indexPath];이런 방식으로 읽을 수 있다.
읽어들인 전체 레코드의 갯수는 아래와 같은 식으로 읽을 수 있다.
id section = [[src sections] objectAtIndex:0]; NSInteger count = [section numberOfObjects];위 예제는 section 0의 모든 레코드 갯수를 구하는 예제이다. 물론 section을 어떻게 쓰느냐에 따라 달라진다.
정체
NSFetchedResultsController 는 컨텍스트(NSManagedObjectContext)의 executeFetchRequest 와 거의 비슷한 기능을 제공한다. DB 에서 내용을 읽고 검색하고 정렬해서 메모리에 로드하는건 동일하다. 다만 batch size 라던가 cache 라던가 등등 메모리 점유율과 퍼포먼스를 위한 추가 기능들이 제공된다.하지만 이것 뿐 만이 아니다. delegate와 관련된 내용이 어떻게 보면 핵심이다.
NSFetchedResultsControllerDelegate 프로토콜
delegate 는 당연히 NSFetchedResultsControllerDelegate 프로토콜을 구현한 객체가 할당되어야 한다. 이 프로토콜은 아래와 같은 정의를 갖는다.- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller; - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath; - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id사실 영어 의미로 무엇인지 유추가 가능하다면 끝인 이야기다.)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type; - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller;
이 프로토콜은 컨트롤러가 담당하고 있는 엔티티(테이블)의 내용이 변화 되었음을 알려주기 위한 용도이다.
CoreData 와 관련된 첫 번째와 두 번째 포스팅에서 삽입, 삭제, 수정에 대한 것을 이야기 했었다. 만약 이런 삽입이나 수정, 삭제 같은 동작이 발생하면 위의 delegate 에 위임받은 셀렉터가 호출된다.
예를 들어 삽입(INSERT)이 일어나면 controller:didChangeObject... 셀렉터가 아래와 같은 정보로 호출된다:
- type 은 NSFetchedResultsChangeInsert
- anObject 에 실제 엔티티 모델 오브젝트가 들어있다. (예에서는 TestEntity 오브젝트)
- indexPath 는 생성일 경우 nil 이 들어있을 것이다. (수정이나 삭제가 아니고선 이건 쓸 일이 없으니까)
- newIndexPath 는 새로 삽입된 오브젝트의 Index Path 가 들어있다.
NSFetchedResultsController 의 가치를 가장 잘 보여주는 예가 바로 이 UITableView 와의 연계이다. 데이터베이스에 레코드가 바로 추가되면 delegate 를 이용해 테이블뷰의 내용을 쉽게 갱신 할 수 있도록 구조화 되어있다. 물론 수정이나 삭제 시에도 마찬가지이다. NSIndexPath 를 그대로 사용하므로써 데이터소스와의 연계도 쉽게 가능하다.
결론
NSFetchedResultsController 를 이용하면 데이터를 관리하는 코드와 UI를 담당하는 로직을 쉽게 분리 할 수 있게 도와준다.만약 네트워크로 내용을 가져와서 표시하는 앱이라면 아래와 같은 식으로 로직을 분리 할 수 있다.
- 네트워크로 필요한 데이터를 다운로드 받고
- 받은 데이터를 코어데이터를 이용해 데이터베이스에 삽입한다.
- 그럼 UI 에서는 자동으로 delegate 를 통해 데이터베이스가 바뀌었다는 것을 통보받고 이 때 UI를 업데이트 하도록 하면 된다.
만약 이런 기능이 없었다면 데이터베이스와 네트워크 등의 로직을 연결해서 순차적으로 동작하게 해야만 한다. 프로그래머로썬 상당히 골치아픈 일이다.
추신) section 에 관해서는 생략했다. 필요하다면 어떻게 쓰는지 찾아보자.
관련포스트:
댓글