Как работать с временными экземплярами NSManagedObject?

86

Мне нужно создать NSManagedObjectэкземпляры, сделать с ними что-то, а затем выбросить их или сохранить в sqlite db. Проблема в том, что я не могу создавать экземпляры NSManagedObjectнеподключенных к, NSManagedObjectContextа это означает, что мне нужно как-то прояснить ситуацию после того, как я решу, что мне не нужны некоторые объекты в моей базе данных.

Чтобы справиться с этим, я создал хранилище в памяти, используя тот же координатор, и я помещаю туда временные объекты с помощью assignObject:toPersistentStore.Теперь, как мне убедиться, что эти временные объекты не попадают в данные, которые я получаю из общий для обоих магазинов контекст? Или мне нужно создавать отдельные контексты для такой задачи?


UPD:

Теперь я думаю о создании отдельного контекста для хранилища в памяти. Как мне перемещать объекты из одного контекста в другой? Просто используя [context insertObject:]? Будет ли нормально работать в этой настройке? Если я вставлю один объект из графа объектов, будет ли весь граф также вставлен в контекст?

духи
источник
Это должен быть отдельный вопрос, поскольку вы отметили его как ответ. Создайте новый вопрос и объясните, ПОЧЕМУ вы считаете, что вам нужен отдельный весь стек Core Data ТОЛЬКО для хранилища в памяти. Я буду рад обсудить с вами этот вопрос.
Marcus S. Zarra
Раздел UPD теперь не актуален, потому что я выбрал другой подход, см. Мой последний комментарий к вашему ответу.
fspirit

Ответы:

146

ПРИМЕЧАНИЕ. Этот ответ очень старый. См. Комментарии для полной истории. С тех пор моя рекомендация изменилась, и я больше не рекомендую использовать несвязанные NSManagedObjectэкземпляры. Моя текущая рекомендация - использовать временные дочерние NSManagedObjectContextэкземпляры.

Оригинальный ответ

Самый простой способ сделать это - создать свои NSManagedObjectэкземпляры без связанного NSManagedObjectContext.

NSEntityDescription *entity = [NSEntityDescription entityForName:@"MyEntity" inManagedObjectContext:myMOC];
NSManagedObject *unassociatedObject = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];

Затем, когда вы захотите его сохранить:

[myMOC insertObject:unassociatedObject];
NSError *error = nil;
if (![myMoc save:&error]) {
  //Respond to the error
}
Маркус С. Зарра
источник
6
Если unassociatedObject имеет ссылки на другие неассоциированные объекты, следует ли мне вставлять их по одному, или myMOC достаточно умен, чтобы собрать все ссылки и также вставить их?
fspirit
6
Он достаточно умен, чтобы справляться и с отношениями.
Marcus S. Zarra
2
Мне нравится, что этот подход позволяет вам обращаться с МО как с обычными объектами данных, прежде чем вы решите их сохранить, но меня беспокоит, насколько «поддерживается» контракт CoreData и, следовательно, насколько он надежен в будущем. Apple упоминает или использует где-нибудь этот подход? Потому что в противном случае будущий выпуск iOS может изменить динамические свойства, чтобы они зависели от MOC, и нарушил этот подход. В документации Apple это неясно: они подчеркивают важность контекста и назначенного инициализатора, но есть одно упоминание в документе MO, говорящее «если контекст не равен нулю, то ...», предполагающий, что ноль может быть нормальным
Rhubarb
41
Я использовал этот подход некоторое время назад, но начал замечать странное поведение и сбои, когда я модифицировал эти объекты и / или создавал для них отношения, прежде чем вставлять их в MOC. Я поговорил об этом с инженером Core Data в WWDC, и он сказал, что, хотя API для несвязанных объектов существует, он настоятельно рекомендовал не использовать его, поскольку MOC сильно зависит от уведомлений KVO, отправляемых его объектами. Он предложил использовать обычный NSObject для временных объектов, так как это намного безопаснее.
Адриан Шёниг
7
Это не очень хорошо работает с iOS 8, особенно с постоянными отношениями. Кто-нибудь еще может это подтвердить?
Янум Триведи
40

iOS5 предоставляет более простую альтернативу ответу Майка Веллера. Вместо этого используйте дочерний NSManagedObjectContext. Устраняет необходимость кататься на батуте через NSNotificationCenter

Чтобы создать дочерний контекст:

NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
childContext.parentContext = myMangedObjectContext;

Затем создайте свои объекты, используя дочерний контекст:

NSManagedObject *o = [NSEntityDescription insertNewObjectForEntityForName:@"MyObject" inManagedObjectContext:childContext];

Изменения применяются только при сохранении дочернего контекста. Так что отменить изменения точно не спасают.

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

NSManagedObjectID *mid = [myManagedObject objectID];
MyManagedObject *mySafeManagedObject = [childContext objectWithID:mid];
object.relationship=mySafeManagedObject;

Обратите внимание, что при сохранении дочернего контекста изменения применяются к родительскому контексту. При сохранении родительского контекста изменения сохраняются.

См. Сессию 214 на wwdc 2012 г. для полного объяснения.

железнодорожный парад
источник
1
Спасибо, что предложили это! Я написал демонстрацию, тестирующую этот метод по сравнению с использованием контекста nil, и, по крайней мере, на OSX, это работало, когда вставка контекста nil теряла свои атрибуты при сохранении - демонстрация на github.com/seltzered/CoreDataMagicalRecordTempObjectsDemo
Вивек Гани
Что находится mocв третьем фрагменте? Это childContextили myMangedObjectContext?
bugloaf
Это ребенок. Контекст
железнодорожный парад
это решение лучше, чем наличие нулевого контекста.
Will Y
Поскольку NSManagedObjectуже предоставлено актуальное NSManagedObjectContext, вы можете автоматизировать выбор контекста: NSManagedObject* objectRelatedContextually = [objectWithRelationship.managedObjectContext objectWithID:objectRelated.objectID];а затем objectWithRelationship.relationship = objectRelatedContextually;.
Гэри
9

Правильный способ добиться такого рода вещей - использовать новый контекст управляемого объекта. Вы создаете контекст управляемого объекта с тем же постоянным хранилищем:

NSManagedObjectContext *tempContext = [[[NSManagedObjectContext alloc] init] autorelease];
[tempContext setPersistentStore:[originalContext persistentStore]];

Затем вы добавляете новые объекты, изменяете их и т. Д.

Когда приходит время сохранять, вам нужно вызвать [tempContext save: ...] в tempContext и обработать уведомление о сохранении, чтобы объединить его с исходным контекстом. Чтобы отбросить объекты, просто отпустите этот временный контекст и забудьте о нем.

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

/* Called when the temp context is saved */
- (void)tempContextSaved:(NSNotification *)notification {
    /* Merge the changes into the original managed object context */
    [originalContext mergeChangesFromContextDidSaveNotification:notification];
}

// Here's where we do the save itself

// Add the notification handler
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(tempContextSaved:)
                                             name:NSManagedObjectContextDidSaveNotification
                                           object:tempContext];

// Save
[tempContext save:NULL];
// Remove the handler again
[[NSNotificationCenter defaultCenter] removeObserver:self
                                                name:NSManagedObjectContextDidSaveNotification
                                              object:tempContext];

Таким же образом следует обрабатывать многопоточные операции с данными ядра. Один контекст на поток.

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

NSManagedObject *objectInOriginalContext = ...;
NSManagedObject *objectInTemporaryContext = [tempContext objectWithID:[objectInOriginalContext objectID]];

Если вы попытаетесь использовать NSManagedObjectнеправильный контекст, вы получите исключения при сохранении.

Майк Веллер
источник
Создание второго контекста только для этого очень расточительно, поскольку установка a требует больших NSManagedObjectContextзатрат как памяти, так и процессора. Я понимаю, что изначально это было в некоторых примерах Apple, но они обновили и исправили эти примеры.
Marcus S. Zarra
2
Apple до сих пор использует эту технику (создание второго контекста управляемого объекта) для примера кода CoreDataBooks.
Nevan King
1
Обратите внимание, что Apple обновила CoreDataBooks, на самом деле он по-прежнему использует два контекста, но теперь второй контекст является дочерним по отношению к первому. Этот метод обсуждается (и рекомендуется) в презентации 303 WWDC 2011 (что нового в Core Data в iOS) и упоминается здесь (с гораздо более простым кодом для объединения изменений вверх) stackoverflow.com/questions/9791469/…
Rhubarb
4
«Создание второго контекста только для этого очень расточительно, поскольку создание NSManagedObjectContext требует больших затрат как памяти, так и процессора». . Нет, это не так. Зависимости координатора постоянного хранилища (модель управляемого объекта и конкретные хранилища) - это не контекст. Контексты легкие.
quellish
3
@quellish Согласен. Apple заявила в своих недавних обсуждениях производительности основных данных на WWDC, что создание контекстов очень легкое.
Джесси
9

Создание временных объектов из контекста nil отлично работает до тех пор, пока вы действительно не попытаетесь установить связь с объектом, контекст которого! = Nil!

убедитесь, что вы согласны с этим.

user134611
источник
Я не согласен с этим
Чарли
8

То, что вы описываете, - это именно то, NSManagedObjectContextдля чего предназначен.

Из Руководства по программированию основных данных: Основы основных данных

Вы можете думать о контексте управляемого объекта как о интеллектуальной блокноте. Когда вы выбираете объекты из постоянного хранилища, вы переносите временные копии в блокнот, где они формируют граф объектов (или коллекцию графов объектов). Затем вы можете изменить эти объекты по своему усмотрению. Однако до тех пор, пока вы не сохраните эти изменения, постоянное хранилище останется неизменным.

И Руководство по программированию основных данных: проверка управляемых объектов

Это также подкрепляет идею контекста управляемого объекта, представляющего «блокнот» - в общем, вы можете перенести управляемые объекты в блокнот и редактировать их по своему усмотрению, прежде чем в конечном итоге либо зафиксировать изменения, либо отменить их.

NSManagedObjectContexts разработаны, чтобы быть легкими. Вы можете создавать и отбрасывать их по желанию - это постоянный координатор хранилищ, и его зависимости являются «тяжелыми». С одним постоянным координатором хранилища может быть связано множество контекстов. В более старой, устаревшей модели ограничения потоков это означало бы установку одного и того же постоянного координатора хранилища для каждого контекста. Сегодня это будет означать подключение вложенных контекстов к корневому контексту, который связан с постоянным координатором хранилища.

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

Попытка создать управляемые объекты независимо от объекта NSManagedObjectContextвызывает проблемы. Помните, что Core Data - это, в конечном счете, механизм отслеживания изменений для графа объектов. По этой причине управляемые объекты действительно являются частью контекста управляемого объекта . Контекст наблюдает за их жизненным циклом , и без контекста не все функции управляемого объекта будут работать правильно.

успокаивать
источник
6

В зависимости от того, как вы используете временный объект, к приведенным выше рекомендациям следует делать некоторые оговорки. В моем случае я хочу создать временный объект и привязать его к представлениям. Когда пользователь решает сохранить этот объект, я хочу установить отношения с существующими объектами и сохранить. Я хочу сделать это, чтобы избежать создания временного объекта для хранения этих значений. (Да, я мог бы просто подождать, пока пользователь сохранит, а затем захватить содержимое представления, но я помещаю эти представления внутри таблицы, и логика для этого менее элегантна.)

Возможные варианты временных объектов:

1) (Предпочтительно) Создайте временный объект в дочернем контексте. Это не сработает, потому что я привязываю объект к пользовательскому интерфейсу и не могу гарантировать, что средства доступа к объекту будут вызваны в дочернем контексте. (Я не нашел никакой документации, в которой говорится об обратном, поэтому я должен предположить.)

2) Создайте временный объект с нулевым контекстом объекта. Это не работает и приводит к потере / повреждению данных.

Мое решение: я решил это, создав временный объект с нулевым контекстом объекта, но когда я сохраняю объект, а не вставляю его как № 2, я копирую все его атрибуты в новый объект, который я создаю в основном контексте. Я создал вспомогательный метод в своем подклассе NSManagedObject под названием cloneInto: он позволяет мне легко копировать атрибуты и отношения для любого объекта.

Грег
источник
Вот что я ищу. Но я сомневаюсь, как вы будете обрабатывать атрибуты отношений?
Mani
1

Для меня ответ Маркуса не сработал. Вот что у меня сработало:

NSEntityDescription entityForName:@"MyEntity" inManagedObjectContext:myMOC];
NSManagedObject *unassociatedObject = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];

затем, если я решу его сохранить:

[myMOC insertObject:unassociatedObjet];
NSError *error = nil;
[myMoc save:&error];
//Check the error!

Мы также должны не забыть выпустить его

[unassociatedObject release]
Лукас
источник
1

Я переписываю этот ответ для Swift, как и все похожие вопросы для быстрого перенаправления на этот вопрос.

Вы можете объявить объект без какого-либо ManagedContext, используя следующий код.

let entity = NSEntityDescription.entity(forEntityName: "EntityName", in: myContext)
let unassociatedObject = NSManagedObject.init(entity: entity!, insertInto: nil)

Позже, чтобы сохранить объект, вы можете вставить его в контекст и сохранить.

myContext.insert(unassociatedObject)
// Saving the object
do {
    try self.stack.saveContext()
    } catch {
        print("save unsuccessful")
    }
}
Митул Джиндал
источник