[iOS Study] 코어 데이터 |
출처 : 아론 힐리가스의 iOS 프로그래밍
-
데이터를 로컬에 저장하는 방법은 “아카이빙” 또는 “코어 데이터” 를 사용한다.
-
아카이빙의 가장 큰 결점은 전부 다냐 아무것도 아니냐는 특성에 있다.
아카이브 안의 내용에 접근하려면 전체 파일을 언아카이브해야 한다.
변경사항을 저장하려면 전체 파일을 다시 쓰기 해야 한다.
반면 코어 데이터(Core Data)는 저장된 객체의 일부만 가져올 수 있다.
그리고 어떤 객체를 변경한다면 파일의 해당 부분만 갱신할 수 있다.
이러한 점진적 가져오기, 업데이트, 삭제, 삽입은 파일시스템과 메모리 사이를 오가는 많은 모델 객체를 가지는 경우 앱의 급격한 성능 향상을 가져올 수 있다.
-
코어 데이터는 객체-관계형 매핑 ( object-relational mapping )을 제공하는 프레임워크이다.
코어 데이터는 Objective-C 객체를 SQLite 데이터베이스 파일에 저장된 데이터로 변환하거나 그 반대의 경우도 가능하다.
SQLite 는 단일 파일에 저장된 관계형 데이터 베이스이다.
-
아카이빙은 1000개보다 적은 객체에 적합하다.
-
각 테이블은 Objective-C 클래스와 같다.
각 열은 클래스의 프로퍼티 중 하나이다.
따라서 코어 데이터의 역할은 이 둘 사이에서 데이터를 옮기는 것이다.
코어 데이터는 이러한 개념을 설명하는 데 다른 용어를 사용한다.
테이블/클래스는 엔티티(entity).
컬럼/프로퍼티는 애트리뷰트(attribute) 로 표현된다.
코어 데이터 모델 파일은 앱에서 모든 엔티티와 그 애트리뷰트를 서술한다.
-
Core Data 의 DataModel 을 통해 데이터 베이스를 디자인 할 수 있다.
( xcdatamodelid 확장자 파일 )
-
transformable 타입은 변환 가능한 애트리뷰트라는 의미이다.
transformable 을 사용하는 이유는 코어 데이터는 저장소에 정해진 데이터 타입만을 저장할 수 있기 때문이다.
변환 가능한 애트리뷰트는 코어 데이터에서 저장할 때 이 객체를 NSData 로 변환한다.
그리고 다시 파일시스템에서 불러올 때 NSData 를 원 객체로 변환한다.
코어 데이터로 이렇게 하기 위해서는 이 변환을 처리하는 NSValueTransformer 하위 클래스를 함께 제공해야 한다.
-
@implementation ImageTransformer
+(Class)transformedValueClass{
return [NSData class];
}
-(id)transformedVaue:(id)value{
if ( !value ){
return nil;
}
if ( [value isKindOfClass:[NSData data]] ) {
return value;
}
return UIImagePNGRepresentation(value);
}
-(id)reverseTansformedValue:(id)value{
return [UIImage imageWithData:value];
}
@end
-
NSValueTransformer 의 transformedValueClass 클래스 메소드는 transformedValue: 메소드에서 받을 객체가 어떤 객체인지 transformer 에게 알려준다.
transformedValue: 메소드는 변환 가능한 변수가 파일 시스템에 저장될 때 호출된다.
그리고 코어 데이터에 저장될 수 있는 객체를 기대한다.
reverseTransformedValue: 메소드는 데이터가 파일 시스템에서 로드될 때 호출되고 저장되어 있는 NSData 로부터 UIImage 를 만들 것이다.
Transformable 을 변환해줄 class 를 연결해주려면,
엔티티를 선택하고, inspector 에서 Value Transformer Name 플레이스 홀더를 작성한 ValueTransformer 로 바꾸면 된다.
-
엔티티 사이의 관계는 객체 사이의 포인터로 표현한다.
관계에는 일대일 관계와 일대다 관계 두 가지가 있다.
엔티티가 일대일 관계를 가지면 그 엔티티의 각 인스턴스는 그와 관련된 엔티티의 인스턴스에 대한 포인터를 가진다.
엔티티가 일대다 관계를 가지면 그 엔티티의 각 인스턴스는 NSSet 타입의 포인터를 가진다.
-
객체를 코어 데이터로 가져오면 그 클래스는 기본적으로 NSManagedObject 이다.
NSManagedObject 는 NSObject 의 하위 클래스로 코어 데이터의 다른 부분과 협력할 수 있다.
NSManagedObject 는 딕셔너리와 조금 비슷하게 작동한다.
엔티티의 모든 프로퍼티(애트리뷰트 또는 관계)에 대한 키-값 쌍을 저장한다.
-
NSManagedObject 는 데이터 컨테이너보다 좀 더 많은 기능을 한다.
데이터를 저장하는 것외에 뭔가를 하는 모델 객체가 필요하면
NSManagedObject 하위 클래스를 만들어야 한다.
그리고 모델 파일에서 표준 NSManagedObject 가 아닌 이 하위 클래스의 인스턴스가 이 엔티티를 나타내도록 명시해야 한다.
이는 엔티티를 선택하고, 데이터 모델 인스펙터를 선택하여 Class 필드에 해당 Class 를 설정해주면 된다.
-
iOS 섹션에서 Core Data 를 선택하고 NSManagedObject subclass 항목을 고르고
데이터 모델의 체크박스를 선택한 후,
원하는 데이터 모델을 선택하고 완료하면 자동으로 해당 엔티티에 매핑되는 클래스가 생성된다.
이 때 자동으로 생성된 코드는 retain 으로 마킹되어 있는데, 이는 strong 과 같다.
ARC 이전의 strong 프로퍼티는 retain 으로 불렸다.
-
데이터베이스에 객체가 추가되면 그 객체는 awakeFromInsert 메시지를 받는다.
-
데이터베이스와 통신하는 관문은 NSManagedObjectContext 이다.
NSManagedObjectContext 는 NSPersistentStoreCoordinator 를 사용한다.
영구 저장소 코디네이터에 특정 파일명의 SQLite 데이터베이스를 열도록 요청한다.
영구 저장소 코디네이터는 NSManagedObjectModel 의 인스턴스 형태인 모델 파일을 사용한다.
-
영구 저장소 코디네이터는 두 가지를 알아야 한다.
“나의 엔티티는 어떤 것들이고, 그 애트리뷰트와 관계는 무엇인가?"
“데이터를 어디에 저장하고 어디서 불러올 것인가?"
위의 질문에 답하기 위해서는 NSManagedObjectModel 인스턴스를 만들어야 한다.
NSManagedObjectModel 에 .xcdatamodeld 의 엔티티 정보를 저장하고 이 관리 객체 모델로 영구 저장소 코디네이터를 초기화해야 한다.
그리고 NSManagedObjectContext 인스턴스를 만들고 난 후 객체를 저장하고 불러오기 위해 이 영구 저장소 코디네이터를 이 관리 객체 컨텍스트가 사용한다고 명시해야 한다.
-
NSManagedObjectModel* model = [NSManagedObjectModel mergeModelFromBundle:nil];
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
NSSring* path = @"....";
NSURL* storeURL = [NSURL fileURLWithPath:path];
NSError* error;
if( [psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:nil
error:&error] == false ){
@throw [NSException exceptionWithName:@"FileOpenFailure" reason:[error localizedDescription] userInfo:nil];
}
NSManagedObjectContext* context = [[NSManagedObjectContext alloc] init];
context.persistentStoreCoordinator = psc;
-
NSManagedObjectContext 로부터 객체를 다시 가져오려면 NSFetchRequest 를 준비하고 실행해야 한다.
기져오기 요청(fetch request)을 실행한 후에 그 요청 인자에 해당하는 모든 객체의 배열을 얻을 수 있다.
-
가져오기 요청은 객체를 가져오길 원하는 엔티티에 대해 정의한 엔티티 디스크립션이 필요하다.
엔티티 디스크립션에는 엔티티 이름을 명시해준다.
배열에서 객체의 순서를 지정하기 위해 가져오기 요청의 정렬 서술자(sort descriptor) 를 설정할 수 있다.
정렬 서술자는 엔티티의 애트리뷰트에 해당하는 키와 오름차순인지 내림차순인지 나타내는 BOOL 값을 가진다.
-
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription* entityDescription = [NSEntityDescription entityForName:@"MyTable" inManagedObjectContext:context];
request.entity = entityDescription;
NSSortDescription* sortDescription = [NSSortDescriptor sortDescriptorWithKey:@"columnName" ascending:YES];
request.sortDescriptors = @[sd];
NSError* error;
NSArray* result = [context executeFetchRequest:request error:&error];
...
-
인스턴스를 선택해서 가져오려면 그 요청에 서술어(predicate)를 추가하고 그 서술어를 만족하는 객체들만 반환하게 한다.
서술어는 참과 거짓을 판단할 수 있는 조건을 포함한다.
서술어의 포맷 문자열은 매우 길고 복잡할 수 있다.
애플의 [Predicate Programming Guide] 에는 가능한 모든 것이 완전히 설명돼 있다.
배열에 대해서도 서술어를 사용할 수 있다.
NSPredicate *p = [NSPredicate predicateWithFormat:@“valueInDollars >50”];
[request setPredicate:p];
NSArray *expensiveStuff = [allItems filteredArrayUsingPredicate:p];
-
NSManagedObject *mo = [NSEntityDescription insertNewObjectForEntityForName:@“EntityName” inMangedObjectContext:self.context];
위의 명령어 형태를 통해 새로운 아이템을 생성할 수 있다.
-
[self.context deleteObject:mo]
위의 명령어를 통해 아이템을 삭제할 수 있다.
-
만약 코어 데이터의 어떤 SQL 명령이 실행 중인지 궁금하다면 커맨드라인 인자를 사용해 SQLite 데이터베이스와 통신하는 모든 내용을 콘솔에 기록할 수 있다.
Product 메뉴에서 Edit Scheme… 을 선택한다.
Arguments 탭을 누른다.
-com.apple.CoreData.SQLDebug
1
위의 두 인자를 추가한다.
-
관계는 지연 방식으로 가져오게 된다.
관계와 함께 관리 객체를 가져올 때 이 관계의 상대편에 있는 객체는 가져오지 않는다.
대신에 코어 데이터는 폴트(faults)를 사용한다.
폴트는 더 큰 객체들이 실제로 필요해질 때까지 관계의 종단에 제공되는 경량 플레이스홀더 객체이다.
이것은 객체 관리에서 성능과 메모리 사용량 둘 다에 이득을 준다.
일대다 폴트(집합을 대신하는)와 일대일 폴트(관리 객체를 대신하는)가 있다.
-
객체 폴트는 어느 엔티티에서 왔고 엔티티의 기본 키가 무엇인지 알고 있다.
-
지연 방식으로 가져오면 코어 데이터를 사용하기 쉬울 뿐만 아니라 상당히 효율적이다.
-
일대 다 폴트는 NSSet 을 대신하는 집합 폴트를 가진다.
-
코어 데이터는 매우 강력하고 유연한 영구 저장 프레임워크이다.
좀 더 자세히 알고 싶다면 애플의 [Core Data Programming Guide] 를 읽어볼 것을 강력히 추천한다.
다음의 것들을 알 수 있다.
NSFetchRequest 는 영구 저장소에서 원하는 데이터를 지정하는 강력한 기법이다.
NSPredicate, NSSortDescription, NSExpressionDescription, NSExpression 과 같은 관련 클래스들에 대해 알 수 있다.
그리고 가져오기 요청 템플릿을 모델 파일의 일부로 만들 수도 있다.
가져온 프로터피(fetched property)는 일대다 관계와 비슷하고 NSFetchRequest 와도 비슷하다.
일반적으로 모델 파일에 이를 명시한다.
앱의 버전이 올라갈수록 시간에 따라 데이터 모델을 변경해야 할 것이다.
이는 복잡한 데, [Data Model Versioning and Data Migration Programming Guide] 를 참조하면 좋다.
데이터가 NSManagedObject 인스턴스로 들어가고 다시 관리 객체에서 영구 저장소로 이동할 때의 데이터 검증은 충분히 지원되고 있다.
하나 이상의 영구 저장소와 함께 작동하는 단일 NSManagedObjectContext 를 가질 수 있다.
모델을 구성(configurations) 단위로 나누고 각 구성을 특정 영구 저장소에 할당한다.
각기 다른 저장소에 있는 엔티티 간의 관계를 허용하지 않지만 가져온 프로퍼티를 사용하여 비슷한 결과를 만들 수 있다.
-
영구 저장기법은 장단점이 있다.
아카이빙
정렬된 관계를 허용하며, 버전 관리가 쉽다는 장점.
모든 객체를 읽어들이며, 점진적 업데이트를 할 수 없다는 단점
웹 서비스
다른 장치나 앱에서 데이터 공유가 쉽다는 장점.
서버와 인터넷 연결이 필요하다는 단점.
코어 데이터
기본적으로 지연 가져오기를 하며, 점진적 업데이트가 가능하다는 장점.
버전 관리가 힘들며 ( NSModelMapping 을 이용하면 되지만, ), 일대다 관계에 순서가 있더라도 실제로 엔티티 내에서 정렬되지 않는다는 단점.
cf) CoreData 관련된 header 들은 @import CoreData; 로 대체될 수 있다.
'프로그래밍 놀이터 > iOS' 카테고리의 다른 글
[iOS Study] NSUserDefaults (2) | 2016.03.27 |
---|---|
[iOS Study] 상태 복원 (0) | 2016.03.26 |
[iOS Study] 지역화 (0) | 2016.03.17 |
[iOS Study] UISplitViewController (0) | 2016.03.16 |
[iOS Study] 웹 서비스와 UIWebView (0) | 2016.03.15 |
댓글