[ios] iOS 5에서 빠르고 효율적인 핵심 데이터 가져 오기 구현

질문 : 내 NSFetchedResultsController가 UI를 업데이트하도록 트리거하도록 부모 컨텍스트에서 변경 사항이 유지되도록 자식 컨텍스트를 얻으려면 어떻게해야합니까?

설정은 다음과 같습니다.

많은 XML 데이터를 다운로드하고 추가하는 앱이 있습니다 (약 2 백만 개의 레코드, 각각 일반 텍스트 단락 크기). sqlite 파일의 크기는 약 500MB가됩니다. 이 콘텐츠를 Core Data에 추가하는 데는 시간이 걸리지 만 데이터가 데이터 저장소에 점진적으로로드되는 동안 사용자가 앱을 사용할 수 있기를 원합니다. 많은 양의 데이터가 이동되고 있다는 사실은 사용자가 눈에 띄지 않고 인식 할 수 없어야합니다. 따라서 중단이나 지터가 없습니다. 버터처럼 스크롤합니다. 그래도 앱이 더 유용하고 더 많은 데이터가 추가되므로 데이터가 Core Data 저장소에 추가되기를 영원히 기다릴 수 없습니다. 코드에서 이것은 가져 오기 코드에서 다음과 같은 코드를 피하고 싶다는 것을 의미합니다.

[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];

이 앱은 iOS 5 전용이므로 지원해야하는 가장 느린 기기는 iPhone 3GS입니다.

현재 솔루션을 개발하기 위해 지금까지 사용한 리소스는 다음과 같습니다.

Apple의 핵심 데이터 프로그래밍 가이드 : 효율적인 데이터 가져 오기

  • Autorelease 풀을 사용하여 메모리 유지
  • 관계 비용. 플랫 가져 오기, 마지막에 관계 패치
  • 도움이된다면 쿼리하지 마세요. O (n ^ 2) 방식으로 속도가 느려집니다.
  • 일괄 가져 오기 : 저장, 재설정, 비우기 및 반복
  • 가져올 때 실행 취소 관리자 끄기

iDeveloper TV-핵심 데이터 성능

  • 3 가지 컨텍스트 사용 : 마스터, 메인 및 제한 컨텍스트 유형

iDeveloper TV-Mac, iPhone 및 iPad 용 핵심 데이터 업데이트

  • performBlock으로 다른 큐에 저장을 실행하면 작업이 빨라집니다.
  • 암호화는 속도를 늦추므로 가능하면 끄십시오.

Marcus Zarra의 핵심 데이터에서 대용량 데이터 세트 가져 오기 및 표시

  • 현재 실행 루프에 시간을 제공하여 가져 오기 속도를 늦출 수 있으므로 사용자에게 원활하게 느껴집니다.
  • 샘플 코드는 대규모 가져 오기를 수행하고 UI 응답 성을 유지할 수 있지만 3 개의 컨텍스트 및 디스크에 비동기 저장을 사용하는 것만 큼 빠르지는 않음을 증명합니다.

내 현재 솔루션

NSManagedObjectContext의 3 개의 인스턴스가 있습니다.

masterManagedObjectContext- 이것은 NSPersistentStoreCoordinator가 있고 디스크에 저장을 담당하는 컨텍스트입니다. 이렇게하면 저장이 비동기식이되어 매우 빠릅니다. 다음과 같이 시작할 때 생성합니다.

masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[masterManagedObjectContext setPersistentStoreCoordinator:coordinator];

mainManagedObjectContext -UI가 모든 곳에서 사용하는 컨텍스트입니다. masterManagedObjectContext의 자식입니다. 다음과 같이 만듭니다.

mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[mainManagedObjectContext setUndoManager:nil];
[mainManagedObjectContext setParentContext:masterManagedObjectContext];

backgroundContext- 이 컨텍스트는 XML 데이터를 Core Data로 가져 오는 역할을하는 NSOperation 하위 클래스에서 생성됩니다. 작업의 주요 방법에서 생성하고 마스터 컨텍스트에 연결합니다.

backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundContext setUndoManager:nil];
[backgroundContext setParentContext:masterManagedObjectContext];

이것은 실제로 매우 매우 빠르게 작동합니다. 이 3 가지 컨텍스트 설정을 통해 가져 오기 속도를 10 배 이상 향상시킬 수있었습니다! 솔직히 이것은 믿기 어렵다. (이 기본 디자인은 표준 핵심 데이터 템플릿의 일부 여야합니다 …)

가져 오기 과정에서 두 가지 방법을 저장합니다. 배경 컨텍스트에 저장하는 모든 항목 1000 개 :

BOOL saveSuccess = [backgroundContext save:&error];

그런 다음 가져 오기 프로세스가 끝날 때 마스터 / 부모 컨텍스트를 저장합니다.이 컨텍스트는 표면적으로 기본 컨텍스트를 포함하여 다른 자식 컨텍스트로 수정 사항을 푸시합니다.

[masterManagedObjectContext performBlock:^{
   NSError *parentContextError = nil;
   BOOL parentContextSaveSuccess = [masterManagedObjectContext save:&parentContextError];
}];

문제 : 문제는 뷰를 다시로드 할 때까지 UI가 업데이트되지 않는다는 것입니다.

NSFetchedResultsController를 사용하여 데이터를 공급받는 UITableView가있는 간단한 UIViewController가 있습니다. 가져 오기 프로세스가 완료되면 NSFetchedResultsController가 부모 / 마스터 컨텍스트에서 변경 사항이 없음을 확인하므로 UI가 익숙한 것처럼 자동으로 업데이트되지 않습니다. UIViewController를 스택에서 꺼내고 다시로드하면 모든 데이터가 거기에 있습니다.

질문 : 내 NSFetchedResultsController가 UI를 업데이트하도록 트리거하도록 부모 컨텍스트에서 변경 사항이 유지되도록 자식 컨텍스트를 얻으려면 어떻게해야합니까?

나는 앱을 중단시키는 다음을 시도했습니다.

- (void)saveMasterContext {
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
    [notificationCenter addObserver:self selector:@selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];

    NSError *error = nil;
    BOOL saveSuccess = [masterManagedObjectContext save:&error];

    [notificationCenter removeObserver:self name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
}

- (void)contextChanged:(NSNotification*)notification
{
    if ([notification object] == mainManagedObjectContext) return;

    if (![NSThread isMainThread]) {
        [self performSelectorOnMainThread:@selector(contextChanged:) withObject:notification waitUntilDone:YES];
        return;
    }

    [mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}



답변

마스터 MOC도 스트라이드로 저장해야합니다. MOC가 저장이 끝날 때까지 기다리는 것은 의미가 없습니다. 자체 스레드가 있으며 메모리도 낮게 유지하는 데 도움이됩니다.

당신은 다음과 같이 썼습니다.

그런 다음 가져 오기 프로세스가 끝날 때 마스터 / 부모 컨텍스트를 저장합니다.이 컨텍스트는 표면적으로 기본 컨텍스트를 포함하여 다른 자식 컨텍스트로 수정 사항을 푸시합니다.

구성에는 두 개의 하위 (기본 MOC 및 백그라운드 MOC)가 있으며 둘 다 “마스터”의 상위입니다.

자녀를 저장하면 변경 사항을 부모에게 푸시합니다. 해당 MOC의 다른 하위 항목은 다음에 가져 오기를 수행 할 때 데이터를 볼 수 있습니다. 명시 적으로 알림을받지는 않습니다.

따라서 BG가 저장하면 데이터가 MASTER로 푸시됩니다. 그러나이 데이터는 MASTER가 저장할 때까지 디스크에 없습니다. 또한 MASTER가 디스크에 저장할 때까지 새 항목은 영구 ID를 얻지 못합니다.

시나리오에서는 DidSave 알림 중에 MASTER 저장에서 병합하여 데이터를 MAIN MOC로 가져옵니다.

작동해야하므로 “매달려”있는 위치가 궁금합니다. 나는 당신이 표준적인 방식으로 메인 MOC 스레드에서 실행되고 있지 않다는 점에 주목할 것이다 (적어도 iOS 5에서는 아님).

또한 마스터 MOC의 변경 사항을 병합하는 데만 관심이있을 것입니다. 저장시 업데이트 알림을 사용한다면 이렇게 할 것입니다.

- (void)contextChanged:(NSNotification*)notification {
    // Only interested in merging from master into main.
    if ([notification object] != masterManagedObjectContext) return;

    [mainManagedObjectContext performBlock:^{
        [mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];

        // NOTE: our MOC should not be updated, but we need to reload the data as well
    }];
}

자, 교수형과 관련하여 당신의 진짜 문제가 무엇인지 … 당신은 마스터를 저장하기 위해 두 가지 다른 호출을 보여줍니다. 첫 번째는 자체 performBlock에서 잘 보호되지만 두 번째는 그렇지 않습니다 (performBlock에서 saveMasterContext를 호출 할 수 있지만 …

하지만이 코드도 변경하겠습니다.

- (void)saveMasterContext {
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
    [notificationCenter addObserver:self selector:@selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];

    // Make sure the master runs in it's own thread...
    [masterManagedObjectContext performBlock:^{
        NSError *error = nil;
        BOOL saveSuccess = [masterManagedObjectContext save:&error];
        // Handle error...
        [notificationCenter removeObserver:self name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
    }];
}

그러나 MAIN은 MASTER의 하위입니다. 따라서 변경 사항을 병합 할 필요가 없습니다. 대신 마스터에서 DidSave를보고 다시 가져 오세요! 데이터는 이미 부모에게 앉아 있으며 귀하가 요청하기를 기다리고 있습니다. 이는 처음부터 부모에 데이터를 보유 할 때의 이점 중 하나입니다.

고려해야 할 또 다른 대안입니다 (그리고 귀하의 결과에 대해 듣고 싶습니다. 이는 많은 데이터입니다) …

배경 MOC를 MASTER의 자식으로 만드는 대신 MAIN의 자식으로 만듭니다.

이거 가져와. BG가 저장할 때마다 자동으로 MAIN으로 푸시됩니다. 이제 MAIN은 save를 호출해야하고 마스터는 save를 호출해야하지만 수행하는 모든 작업은 마스터가 디스크에 저장할 때까지 포인터를 이동하는 것입니다.

이 방법의 장점은 데이터가 백그라운드 MOC에서 애플리케이션 MOC로 바로 이동한다는 것입니다 (그런 다음 저장을 위해 통과).

일부 통과에 대한 처벌은하지만 디스크를 칠 때 모든 무거운는 MASTER에서 수행됩니다. 그리고 performBlock을 사용하여 마스터에서 저장을 시작하면 메인 스레드가 요청을 보내고 즉시 반환합니다.

어떻게되는지 알려주세요!


답변