다중 패스를 사용한 핵심 데이터 마이그레이션의 예 또는 설명?


85

내 iPhone 앱은 핵심 데이터 저장소를 마이그레이션해야하며 일부 데이터베이스는 상당히 큽니다. Apple의 문서에서는 "다중 패스"를 사용하여 데이터를 마이그레이션하여 메모리 사용을 줄일 것을 제안합니다. 그러나 문서는 매우 제한적이며 실제로이를 수행하는 방법을 잘 설명하지 않습니다. 누군가가 나를 좋은 예로 안내하거나 실제로이 작업을 수행하는 방법을 자세히 설명 할 수 있습니까?


실제로 메모리 문제를 겪었습니까? 마이그레이션이 간단합니까 아니면 NSMigrationManager를 사용 하시겠습니까?
Nick Weaver

예, GDB 콘솔은 메모리 경고가 있음을 보여준 다음 제한된 메모리로 인해 앱이 충돌합니다. 경량 마이그레이션과 NSMigrationManager를 모두 시도했지만 지금은 NSMigrationManager를 사용하려고합니다.
Jason

좋아, 변경된 사항에 대해 좀 더 자세히 설명해 주시겠습니까?
Nick Weaver

마침내, 나는 내 대답을 찾았습니다.
Nick Weaver

안녕하세요 제이슨, 질문에서 좋아요를 고칠 수 있습니까?
Yuchen Zhong 2015 년

답변:


174

나는 그들의 문서 에서 Apple이 암시하는 바를 알아 냈다 . 실제로는 매우 쉽지만 분명하기까지는 갈 길이 멀다. 예를 들어 설명을 설명하겠습니다. 초기 상황은 다음과 같습니다.

데이터 모델 버전 1

여기에 이미지 설명 입력 여기에 이미지 설명 입력

"핵심 데이터 저장소가있는 탐색 기반 앱"템플릿을 사용하여 프로젝트를 만들 때 얻는 모델입니다. 나는 그것을 컴파일하고 약간 다른 값을 가진 약 2k 항목을 만들기 위해 for 루프의 도움으로 약간의 타격을가했습니다. NSDate 값을 가진 2.000 개의 이벤트가 있습니다.

이제 다음과 같은 두 번째 버전의 데이터 모델을 추가합니다.

여기에 이미지 설명 입력

데이터 모델 버전 2

차이점은 이벤트 엔티티가 사라지고 두 개의 새 엔티티가 있다는 것입니다. 타임 스탬프를로 저장하는 double하나와 날짜를 NSString.

목표는 모든 버전 1 이벤트를 두 개의 새 엔티티 로 전송 하고 마이그레이션과 함께 값을 변환하는 것입니다. 이로 인해 별도의 엔터티에서 다른 유형으로 각각 두 배의 값이 생성됩니다.

마이그레이션을 위해 우리는 수동 마이그레이션을 선택하고 매핑 모델을 사용합니다. 이것은 또한 귀하의 질문에 대한 답변의 첫 번째 부분입니다. 2k 항목을 마이그레이션하는 데 시간이 오래 걸리고 메모리 풋 프린트를 낮게 유지하기를 원하기 때문에 두 단계로 마이그레이션을 수행 할 것입니다.

계속해서 이러한 매핑 모델을 더 분할하여 엔티티 범위 만 마이그레이션 할 수도 있습니다. 백만 개의 레코드가 있다고 가정하면 전체 프로세스가 중단 될 수 있습니다. Filter predicate를 사용하여 가져온 항목의 범위를 좁힐 수 있습니다 .

두 가지 매핑 모델로 돌아갑니다.

다음과 같이 첫 번째 매핑 모델을 만듭니다.

1. 새 파일-> 리소스-> 매핑 모델 여기에 이미지 설명 입력

2. 이름을 선택하고 StepOne을 선택했습니다.

3. 소스 및 대상 데이터 모델 설정

여기에 이미지 설명 입력

모델 매핑 1 단계

여기에 이미지 설명 입력

여기에 이미지 설명 입력

여기에 이미지 설명 입력

멀티 패스 마이그레이션에는 사용자 지정 엔터티 마이그레이션 정책이 필요하지 않지만이 예제에 대해 좀 더 자세히 알아보기 위해 수행합니다. 따라서 엔터티에 사용자 지정 정책을 추가합니다. 이것은 항상의 하위 클래스입니다 NSEntityMigrationPolicy.

여기에 이미지 설명 입력

이 정책 클래스는 마이그레이션을 수행하는 몇 가지 방법을 구현합니다. 그러나이 경우에는 간단하므로 하나의 메소드 만 구현해야합니다 createDestinationInstancesForSourceInstance:entityMapping:manager:error:..

구현은 다음과 같습니다.

StepOneEntityMigrationPolicy.m

#import "StepOneEntityMigrationPolicy.h"


@implementation StepOneEntityMigrationPolicy

- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance 
                                      entityMapping:(NSEntityMapping *)mapping 
                                            manager:(NSMigrationManager *)manager 
                                              error:(NSError **)error
{
    // Create a new object for the model context
    NSManagedObject *newObject = 
        [NSEntityDescription insertNewObjectForEntityForName:[mapping destinationEntityName] 
                                      inManagedObjectContext:[manager destinationContext]];

    // do our transfer of nsdate to nsstring
    NSDate *date = [sInstance valueForKey:@"timeStamp"];
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
    [dateFormatter setDateStyle:NSDateFormatterMediumStyle];    

    // set the value for our new object
    [newObject setValue:[dateFormatter stringFromDate:date] forKey:@"printedDate"];
    [dateFormatter release];

    // do the coupling of old and new
    [manager associateSourceInstance:sInstance withDestinationInstance:newObject forEntityMapping:mapping];

    return YES;
}

마지막 단계 : 마이그레이션 자체

NSDate를 double로 변환하는 데 사용되는 timeIntervalSince1970 만 거의 동일한 두 번째 매핑 모델을 설정하는 부분은 건너 뛰겠습니다.

마지막으로 마이그레이션을 트리거해야합니다. 지금은 상용구 코드를 건너 뛰겠습니다. 필요한 경우 여기에 게시하겠습니다. 마이그레이션 프로세스 사용자 지정에서 찾을 수 있으며 처음 두 코드 예제를 병합 한 것입니다. 다음과 같이 세 번째와 마지막 부분은 수정됩니다 : 대신의 클래스 메소드 사용하는 NSMappingModel클래스를 mappingModelFromBundles:forSourceModel:destinationModel:우리가 사용됩니다 initWithContentsOfURL:클래스 방법은 단 하나, 번들 어쩌면 처음 발견 매핑 모델을 반환하기 때문이다.

이제 루프의 모든 단계에서 사용할 수있는 두 가지 매핑 모델이 있으며 마이그레이션 메서드를 마이그레이션 관리자에게 보냅니다. 그게 다야.

NSArray *mappingModelNames = [NSArray arrayWithObjects:@"StepOne", @"StepTwo", nil];
NSDictionary *sourceStoreOptions = nil;

NSURL *destinationStoreURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CoreDataMigrationNew.sqlite"];

NSString *destinationStoreType = NSSQLiteStoreType;

NSDictionary *destinationStoreOptions = nil;

for (NSString *mappingModelName in mappingModelNames) {
    NSURL *fileURL = [[NSBundle mainBundle] URLForResource:mappingModelName withExtension:@"cdm"];

    NSMappingModel *mappingModel = [[NSMappingModel alloc] initWithContentsOfURL:fileURL];

    BOOL ok = [migrationManager migrateStoreFromURL:sourceStoreURL
                                               type:sourceStoreType
                                            options:sourceStoreOptions
                                   withMappingModel:mappingModel
                                   toDestinationURL:destinationStoreURL
                                    destinationType:destinationStoreType
                                 destinationOptions:destinationStoreOptions
                                              error:&error2];
    [mappingModel release];
} 

메모

  • 매핑 모델 cdm은 번들로 끝납니다 .

  • 대상 저장소를 제공해야하며 원본 저장소가 아니어야합니다. 마이그레이션에 성공한 후 이전 항목을 삭제하고 새 이름을 바꿀 수 있습니다.

  • 매핑 모델을 만든 후 데이터 모델을 약간 변경했는데 이로 인해 호환성 오류가 발생하여 매핑 모델을 다시 만들어야 만 해결할 수있었습니다.


59
복잡한 지옥. Apple은 무엇을 생각하고 있었습니까?
aroth

7
잘 모르겠지만 핵심 데이터가 좋은 아이디어라고 생각할 때마다 더 간단하고 유지 관리 가능한 솔루션을 찾으려고 노력합니다.
Nick Weaver

5
감사! 이것은 훌륭한 대답입니다. 복잡해 보이지만 단계를 배우면 그렇게 나쁘지는 않습니다. 가장 큰 문제는 문서가 이와 같이 설명하지 않는다는 것입니다.
bentford

2
다음은 마이그레이션 프로세스 사용자 지정에 대한 업데이트 된 링크입니다. 이 게시물이 작성된 이후로 이동했습니다. developer.apple.com/library/ios/documentation/Cocoa/Conceptual/…
user1021430

@NickWeaver destinationStoreURL을 어떻게 결정하고 있습니까? 작성 중이거나 마이그레이션 프로세스 중에 핵심 데이터 시스템에서 작성됩니까 ????
dev gr

3

다음 질문은 관련이 있습니다.

iPhone에서 대용량 CoreData 데이터 저장소를 마이그레이션하는 메모리 문제

iOS를 사용한 청크의 다중 패스 코어 데이터 마이그레이션

첫 번째 링크를 인용하려면 :

이것은 "Multiple Passes"섹션의 공식 문서에서 논의되지만 제안 된 접근 방식은 엔티티 유형별로 마이그레이션을 나누는 것입니다. 즉, 여러 매핑 모델을 만들고, 각각은 완전한 데이터 모델.


1
링크 주셔서 감사합니다. 문제는 아무도 실제로 여러 단계에서 설정 하는 방법 을 자세히 설명하지 않는다는 것 입니다. 효과적으로 작동하려면 여러 매핑 모델을 어떻게 설정해야합니까?
Jason

-5

데이터베이스 스키마에 person, student, course, class, registration 등 5 개의 엔티티가 있다고 가정 해 보겠습니다. 여기에서 student는 person을 하위 클래스로, 클래스는 코스를 구현하며 등록은 class와 student에 합류합니다. 이러한 모든 테이블 정의를 변경 한 경우 기본 클래스에서 시작하여 작업을 진행해야합니다. 따라서 각 등록 기록은 수업과 학생이 거기에 있는지에 따라 다르기 때문에 등록 변환으로 시작할 수 없습니다. 따라서 Person 테이블 만 마이그레이션하고 기존 행을 새 테이블에 복사하고 가능한 경우 새 필드를 채우고 제거 된 열을 버리는 것으로 시작합니다. 자동 릴리스 풀 내에서 각 마이그레이션을 수행하여 완료되면 메모리가 다시 시작되도록합니다.

Person 테이블이 완료되면 학생 테이블을 다시 변환 할 수 있습니다. 그런 다음 Course, Class, 마지막으로 Registration 테이블로 이동합니다.

다른 고려 사항은 레코드의 수입니다. Person처럼 행이 천 개이면 100 개 정도마다 릴리스에 해당하는 NSManagedObject를 실행해야합니다. 이는 관리되는 개체 컨텍스트에 알리는 것입니다 [moc refreshObject : ob mergeChanges : 아니]; 또한 오래된 데이터 타이머를 낮게 설정하여 메모리가 자주 플러시되도록하십시오.


그래서 본질적으로 이전 스키마의 일부가 아닌 새로운 핵심 데이터 스키마를 가지고 데이터를 새 스키마에 직접 복사 할 것을 제안하고 있습니까?
Jason

-1 데이터베이스를 수동으로 매핑 할 필요가 없습니다. 경량 마이그레이션 또는 명시 적 MappingModels를 사용하여 배포 된 데이터베이스를 마이그레이션 할 수 있습니다.
bentford
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.