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


101

질문 : 내 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];
}

26
가장 잘 형성되고 준비된 질문에 +1000000. 나도 대답이 있습니다 ... 입력하는 데 몇 분 정도 걸립니다 ...
Jody Hagins

1
앱이 중단되었다고 할 때 어디에 있습니까? 뭐하는거야?
Jody Hagins

오랜만에 불러서 죄송합니다. "플랫 가져 오기 후 마지막에 관계 패치"가 무엇을 의미하는지 설명해 주시겠습니까? 관계를 설정하기 위해 여전히 그 객체를 기억해야하지 않습니까? 나는 당신과 매우 유사한 솔루션을 구현하려고 노력하고 있으며 실제로 메모리 사용량을 줄이는 데 도움을 줄 수 있습니다.
Andrea Sprega

이 기사의 첫 번째에 링크 된 Apple 문서를 참조하십시오. 이것을 설명합니다. 행운을 빕니다!
David Weiss

1
정말 좋은 질문 나는 당신이 당신의 설정으로 제공하는 설명에서 몇 가지 깔끔한 트릭을 집어
djskinner

답변:


47

마스터 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을 사용하여 마스터에서 저장을 시작하면 메인 스레드가 요청을 보내고 즉시 반환합니다.

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


훌륭한 대답입니다. 오늘이 아이디어를 시도해보고 내가 발견 한 것을 볼 것입니다. 감사합니다!
David Weiss

대박! 완벽하게 작동했습니다! 그래도 MASTER-> MAIN-> BG에 대한 당신의 제안을 시도해보고 그 성능이 어떻게 작동하는지 볼 것입니다. 매우 흥미로운 아이디어처럼 보입니다. 훌륭한 아이디어에 감사드립니다!
David Weiss

4
performBlockAndWait를 performBlock으로 변경하도록 업데이트되었습니다. 왜 이것이 내 대기열에 다시 나타나는지 확실하지 않지만 이번에 읽었을 때 분명했습니다 ... 왜 전에 놓았는지 확실하지 않습니다. 예, performBlockAndWait은 재진입이 가능합니다. 그러나 이와 같은 중첩 환경에서는 상위 컨텍스트 내에서 하위 컨텍스트에 대한 동기 버전을 호출 할 수 없습니다. 알림은 부모 컨텍스트에서 전송 될 수 있으며 (이 경우) 교착 상태를 유발할 수 있습니다. 나중에이 글을 읽고 읽는 모든 분들에게 분명해지기를 바랍니다. 고마워, 데이비드.
Jody Hagins 2012-07-15

1
@DavidWeiss MASTER-> MAIN-> BG를 사용해 보셨습니까? 이 디자인 패턴에 관심이 있으며 그것이 당신에게 잘 맞는지 알고 싶습니다. 감사합니다.
nonamelive

2
MASTER와 문제는 -> 주요 - 당신이 BG 컨텍스트에서 가져 오는 경우> BG 패턴, 그것은 또한 홈페이지에서 가져옵니다 그것은 차단 UI를 당신이 반응하지되는 앱 만들기
Rostyslav
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.