program story

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

inputbox 2020. 8. 24. 08:19
반응형

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가 업데이트되지 않는다는 것입니다.

I have a simple UIViewController with a UITableView that is being fed data using a NSFetchedResultsController. When the Import process completes, the NSFetchedResultsController see's no changes from the parent/master context and so the UI doesn't automatically update like I'm used to seeing. If I pop the UIViewController off the stack and load it again all the data is there.

Question: How do I get my child context to see changes persisted on the parent context so that they trigger my NSFetchedResultsController to update the UI?

I have tried the following which just hangs the app:

- (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];
}

You should probably save the master MOC in strides as well. No sense having that MOC wait until the end to save. It has its own thread, and it will help keep memory down as well.

You wrote:

Then at the end of the import process, I save on the master/parent context which, ostensibly, pushes modifications out to the other child contexts including the main context:

In your configuration, you have two children (the main MOC and the background MOC), both parented to the "master."

When you save on a child, it pushes the changes up into the parent. Other children of that MOC will see the data the next time they perform a fetch... they are not explicitly notified.

So, when BG saves, its data is pushed to MASTER. Note, however, that none of this data is on disk until MASTER saves. Furthermore, any new items will not get permanent IDs until the MASTER saves to disk.

In your scenario, you are pulling the data into the MAIN MOC by merging from the MASTER save during the DidSave notification.

That should work, so I'm curious as to where it is "hung." I will note, that you are not running on the main MOC thread in the canonical way (at least not for iOS 5).

Also, you probably only are interested in merging changes from the master MOC (though your registration looks like it is only for that anyway). If I were to use the update-on-did-save-notification, I'd do this...

- (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
    }];
}

Now, for what may be your real issue regarding the hang... you show two different calls to save on the master. the first is well protected in its own performBlock, but the second is not (though you may be calling saveMasterContext in a performBlock...

However, I'd also change this code...

- (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];
    }];
}

However, note that the MAIN is a child of MASTER. So, it should not have to merge the changes. Instead, just watch for the DidSave on the master, and just refetch! The data is sitting in your parent already, just waiting for you to ask for it. That's one of the benefits of having the data in the parent in the first place.

Another alternative to consider (and I'd be interested to hear about your results -- that's a lot of data)...

Instead of making the background MOC a child of the MASTER, make it a child of the MAIN.

Get this. Every time the BG saves, it automatically gets pushed into the MAIN. Now, the MAIN has to call save, and then the master has to call save, but all those are doing is moving pointers... until the master saves to disk.

The beauty of that method is that the data goes from the background MOC straight into your applications MOC (then passes through to get saved).

There is some penalty for the pass-through, but all the heavy lifting gets done in the MASTER when it hits the disk. And if you kick those saves on the master with performBlock, then main thread just sends off the request, and returns immediately.

Please let me know how it goes!

참고URL : https://stackoverflow.com/questions/10542097/implementing-fast-and-efficient-core-data-import-on-ios-5

반응형