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

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

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

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

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

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

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

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

    //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");}
    }];
    
  4. Когда мой пользователь сохраняет, он отправляет своему делегату (мой главный контроллер представления) сообщение. Делегату передается объект, который был добавлен, и он должен найти этот же объект в главном контексте. Но когда я ищу его в основном контексте, он не обнаруживается. Основной контекст содержит сущность - я могу зарегистрировать ее детали и подтвердить, что она есть, но адрес другой? Если это должно произойти (почему?), Как я могу найти добавленный объект в главном контексте после сохранения?

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

3 ответа

Решение

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

  1. Традиционно вы используете NSMainQueueConcurrencyType для NSManagedObjectContext связанный с основным потоком, и NSPrivateQueueConcurrencyTypes для детских контекстов. Дочерний контекст не должен совпадать с его родителем. NSConfinementConcurrencyType это то, что все NSManagedObjectContextпо умолчанию, если вы не указали тип. По сути, это тип "Я буду управлять своими собственными потоками для базовых данных".
  2. Не видя ваш код, я предполагаю, что область, в которой вы создаете дочерний контекст, заканчивается, и он очищается.
  3. При использовании шаблона родительского / дочернего контекста вам необходимо использовать блочные методы. Самым большим преимуществом использования блочных методов является то, что ОС будет обрабатывать вызовы методов для правильных потоков. Ты можешь использовать 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");
        }
    }];
}

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

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

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

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

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

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

[childContext reset];

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

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

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

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

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

    - objectRegisteredForID:

    - objectWithID:

    - existingObjectWithID:error:

Другие вопросы по тегам