Правильная реализация родительского/дочернего NSManagedObjectContext

Мое приложение иногда вставляет объекты в контекст управляемого объекта, которые не должны обязательно сохраняться. Например, когда я запускаю мода "добавить сущность", я создаю управляемый объект и назначаю его модальному. Если пользователь сохраняет этот модальный режим, я сохраняю контекст. Если он отменяет, я удаляю объект, и сохранение не требуется.

Теперь я представил функцию импорта, которая переключается на мое приложение (с использованием схемы URL) и добавляет объект. Поскольку один из этих модалов может быть открытым, небезопасно сохранять контекст на этом этапе. Временный объект, созданный для модального, будет сохранен, даже если пользователь отменяет, и нет гарантии, что удаление (из операции отмены) будет сохранено позже - пользователь может выйти из приложения.

Точно так же я не могу просто сохранять, когда мое приложение завершается. Если модальность открыта в этой точке, временный объект будет неправильно сохранен.

Чтобы решить эту проблему, я пытаюсь использовать дочерний контекст, как обсуждалось здесь. Прочитав все, что я смог найти на SO, у меня возникло несколько вопросов:

  • Какой тип concurrency следует использовать для каждого контекста? Помните, что я не делаю этого для преимуществ производительности/потоков. Я знаю, что не могу использовать NSConfinementConcurrencyType для основного контекста, если у него есть дочерние контексты, но я не уверен, какой из двух других вариантов лучше всего подходит. Что касается детского контекста, нужно ли ему сопоставлять? Или я могу использовать здесь тип конфайнмента? Я пробовал различные комбинации, и все, кажется, работают нормально, но я хотел бы знать, что подходит для моих требований.

  • (боковая проблема) Почему я могу заставить это работать, если я использую класс iVar? Я думал, что должен уметь объявлять временный контекст в методе, где он создан, а затем позже ссылаться на него с помощью entity.managedObjectContext. Но, к сожалению, к тому моменту, когда я прихожу к нему? Это исправлено, если я вместо этого использую iVar для хранения ссылки.

  • Каков правильный способ или распространение изменений в главном контексте? Я видел различные комментарии, используя разные блокированные реализации в каждом из контекстов. Это зависит от моего типа concurrency? Моя текущая версия:

    //save the new entity in the temporary context
    NSError *error = nil;
    if (![myObject.managedObjectContext save:&error]) {NSLog(@"Error - unable to save new object in its (temporary) context");}
    
    //propogate the save to the main context
    [self.mainContext performBlock:^{
        NSError *error2 = nil;
        if (![self.mainContext save:&error2]) {NSLog(@"Error - unable to merge new entity into main context");}
    }];
    
  • Когда мой пользователь сохраняет, он отправляет своему делегату (моему контроллеру главного представления) сообщение. Делегату передается объект, который был добавлен, и он должен найти тот же самый объект в главном контексте. Но когда я ищу его в основном контексте, он не найден. В главном контексте есть содержится объект - я могу зарегистрировать его данные и подтвердить, что он есть, - но адрес отличается? Если это должно произойти (почему?), Как я могу найти добавленный объект в основном контексте после сохранения?

Спасибо за понимание. Извините за длинный, многочастный вопрос, но я думал, что кто-то, вероятно, рассмотрит все эти проблемы ранее.

Ответ 1

Модель родительского/дочернего MOC - действительно мощная функция Core Data. Это невероятно упростило старую проблему concurrency, с которой мы имели дело. Однако, как вы уже сказали, concurrency не является вашей проблемой. Чтобы ответить на ваши вопросы:

  • Традиционно вы используете NSMainQueueConcurrencyType для NSManagedObjectContext, связанных с основным потоком, и NSPrivateQueueConcurrencyType для дочерних контекстов. Детский контекст не обязательно должен соответствовать его родительскому элементу. NSConfinementConcurrencyType - это то, что все NSManagedObjectContext получают по умолчанию, если вы не укажете тип. Это в основном "Я буду управлять своими потоками для основных данных".
  • Без просмотра кода мое предположение будет областью, в которой вы создаете конец дочернего контекста, и очищается.
  • При использовании шаблона родительского/дочернего контекста вам необходимо использовать методы блоков. Самое большое преимущество использования блочных методов заключается в том, что ОС будет обрабатывать вызовы метода для правильных потоков. Вы можете использовать performBlock для асинхронного выполнения или performBlockAndWait для синхронного выполнения.

Вы использовали бы это, например:

- (void)saveContexts {
    [childContext performBlock:^{
        NSError *childError = nil;
        if ([childContext save:&childError]) {
            [parentContext performBlock:^{
                NSError *parentError = nil;
                if (![parentContext save:&parentError]) {
                    NSLog(@"Error saving parent");
                }
            }];
        } else {
            NSLog(@"Error saving child");
        }
    }];
}

Теперь вам нужно иметь в виду, что изменения, внесенные в дочерний контекст (например, вложенные объекты), не будут доступны родительскому контексту, пока вы не сохраните. Для дочернего контекста родительский контекст является постоянным хранилищем. Когда вы сохраняете, вы передаете эти изменения до родителя, который затем может сохранить их в фактическом постоянном хранилище. Сохраняет изменения propogate на один уровень. С другой стороны, выборка в дочерний контекст будет выводить данные на каждый уровень (через родителя и в дочерний элемент).

  1. Вам нужно использовать некоторую форму objectWithID для управляемого объекта управления. Это самый безопасный (и действительно единственный) способ передачи объектов между контекстами. Как отметил Том Харрингтон в комментариях, вы можете использовать existingObjectWithID:error:, хотя, поскольку objectWithID: всегда возвращает объект, даже если вы передаете недопустимый ID (что может привести к исключениям). Подробнее: Ссылка

Ответ 2

У меня были схожие проблемы, и вот ответы на некоторые части ваших вопросов - 1. Вы должны иметь возможность использовать concurrency тип NSPrivateQueueConcurrencyType или NSMainQueueConcurrencyType 2. Скажем, вы создали временный контекст tempContext с родительским контекстом mainContext (предполагается, что iOS5). В этом случае вы можете просто переместить управляемый объект с tempContext на mainContext на -

object = (Object *)[mainContext objectWithID:object.objectID];

Затем вы можете сохранить сам mainContext.

Возможно, также,

[childContext reset];

если вы хотите reset временный контекст.

Ответ 3

  • Если вы используете родительский/дочерний шаблон, вы обычно объявляете родительский контекст с NSMainQueueConcurrencyType и дочерними контекстами с NSPrivateQueueConcurrencyType. NSConfinementConcurrencyType используется для классического шаблона резьбы.

  • Если вы хотите сохранить контекст, вам почему-то нужна сильная ссылка на него.

  • Вы просто вызываете метод сохранения в дочернем контексте, чтобы вносить изменения в родительский контекст, если вы хотите сохранить данные, вы также вызываете save в родительском контексте. Вам не нужно делать это внутри блока.

  • Существует несколько способов получения определенного объекта из контекста. Я не могу сказать вам, какой из них будет работать в вашем случае, попробуйте:

    - objectRegisteredForID:

    - objectWithID:

    - existingObjectWithID:error: