Питання : Як я можу дозволити моєму дочірньому контексту побачити зміни, які зберігаються у батьківському контексті, щоб вони викликали мій NSFetchedResultsController для оновлення інтерфейсу користувача?
Ось налаштування:
У вас є додаток, яке завантажує та додає багато XML-даних (близько 2 мільйонів записів, кожна приблизно розміром із звичайного абзацу тексту) .sqlite-файл має розмір близько 500 Мб. Додавання цього вмісту до основних даних потребує часу, але ви хочете, щоб користувач міг користуватися програмою, тоді як дані поступово завантажуються в сховище даних. Користувачеві повинно бути непомітним і непомітним, що велика кількість даних переміщується навколо, так що ніяких зависань, жодних тремтінь: прокрутки, як масло. Але додаток тим корисніше, чим більше даних додається до нього, тому ми не можемо вічно чекати, коли дані будуть додані до магазину основних даних. У коді це означає, що я дуже хотів би уникати такого коду у коді імпорту:
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
Додаток - лише iOS 5, тому найповільнішим пристроєм, який він потребує, є iPhone 3GS.
Ось ресурси, які я до цього часу використовував для розробки свого поточного рішення:
Основний посібник з програмування даних Apple: ефективний імпорт даних
- Використовуйте пули автоматичного випуску, щоб зберегти пам'ять
- Вартість відносин. Імпортуйте квартиру, а потім закріпіть відносини в кінці
- Не запитуйте, чи можете ви допомогти, це сповільнює ситуацію O (n ^ 2)
- Імпорт партіями: зберегти, скинути, злити та повторити
- Вимкніть диспетчер скасування при імпорті
iDeveloper TV - продуктивність основних даних
- Використовуйте 3 контексти: контекст основного, головного та конфілігентного типів
iDeveloper TV - основні дані для оновлення Mac, iPhone та iPad
- Запуск економить на інших чергах за допомогою performBlock швидко покращує роботу.
- Шифрування сповільнює роботу, виключайте, якщо можете.
Імпорт та показ великих наборів даних у основних даних Маркус Зарра
- Ви можете уповільнити імпорт, надавши час поточному циклу запуску, щоб користувачі не почували себе гладко.
- Зразок коду доводить, що можна імпортувати великі імпорти та підтримувати інтерфейс інтерпретації, але не так швидко, як з 3-ма контекстами та збереженням асинхронізації на диску.
Моє поточне рішення
У мене є 3 екземпляри NSManagedObjectContext:
masterManagedObjectContext - це контекст, який має NSPersistentStoreCoordinator і відповідає за збереження на диску. Я роблю це, щоб мої заощадження могли бути асинхронними і тому дуже швидкими. Я створюю його при запуску так:
masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[masterManagedObjectContext setPersistentStoreCoordinator:coordinator];
mainManagedObjectContext - це контекст, який користувальницький інтерфейс використовує всюди. Це дочірка masterManagedObjectContext. Я створюю це так:
mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[mainManagedObjectContext setUndoManager:nil];
[mainManagedObjectContext setParentContext:masterManagedObjectContext];
backgroundContext - Цей контекст створений у моєму підкласі NSOperation, який відповідає за імпорт XML-даних у основні дані. Я створюю його в основному методі операції і пов'язую його з головним контекстом.
backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundContext setUndoManager:nil];
[backgroundContext setParentContext:masterManagedObjectContext];
Це насправді працює дуже, ДУЖЕ швидко. Тільки зробивши цю 3 конфігурацію контексту, я зміг покращити швидкість імпорту понад 10 разів! Чесно кажучи, у це важко повірити. (Цей базовий дизайн повинен бути частиною стандартного шаблону основних даних ...)
Під час імпортування я зберігаю два різні способи. Кожні 1000 елементів, які я зберігаю у фоновому контексті:
BOOL saveSuccess = [backgroundContext save:&error];
Тоді в кінці процесу імпорту я зберігаю на контексті master / parent, який, нібито, виштовхує модифікації до інших дочірніх контекстів, включаючи основний контекст:
[masterManagedObjectContext performBlock:^{
NSError *parentContextError = nil;
BOOL parentContextSaveSuccess = [masterManagedObjectContext save:&parentContextError];
}];
Проблема : проблема полягає в тому, що мій інтерфейс не оновиться, поки я не перезавантажую представлення даних.
У мене є простий UIViewController з UITableView, який подається даними за допомогою NSFetchedResultsController. Коли процес імпорту завершується, NSFetchedResultsController бачить відсутність змін у батьківському / головному контексті, і тому інтерфейс користувача не оновлюється автоматично, як я звик бачити. Якщо я вискочу UIViewController зі стека та завантажую його знову всі дані є.
Питання : Як я можу дозволити моєму дочірньому контексту побачити зміни, які зберігаються у батьківському контексті, щоб вони викликали мій NSFetchedResultsController для оновлення інтерфейсу користувача?
Я спробував наступне, що просто висить додаток:
- (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];
}