Як мати справу з тимчасовими екземплярами NSManagedObject?


86

Мені потрібно створювати NSManagedObjectекземпляри, робити з ними щось, а потім смітити їх або зберігати в sqlite db. Проблема полягає в тому, що я не можу створювати екземпляри без NSManagedObjectзв’язку з, NSManagedObjectContextі це означає, що я повинен якось очиститись після того, як вирішу, що мені не потрібні деякі об’єкти в моїй базі даних.

Для боротьби з цим я створив сховище в пам’яті, використовуючи той самий координатор, і розміщую там тимчасові об’єкти за допомогою програми assignObject:toPersistentStore.Now, як я можу переконатись, що ці тимчасові об’єкти не потрапляють до даних, які я отримую з спільний для обох магазинів контекст? Або мені доводиться створювати окремі контексти для такого завдання?


UPD:

Зараз я думаю про створення окремого контексту для сховища в пам'яті. Як перемістити об'єкти з одного контексту в інший? Просто використовуючи [context insertObject:]? Чи буде це нормально працювати в цій установці? Якщо я вставляю один об'єкт із графіка об'єктів, чи весь графік також вставляється в контекст?


Це має бути окреме запитання, оскільки ви позначили це як відповідь. Створіть нове запитання та поясніть ЧОМУ ви вважаєте, що вам потрібен окремий цілий стек основних даних ПРОСТО для сховища в пам’яті. Я з радістю розгляну це питання з вами.
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 і, отже, наскільки він захищений у майбутньому. Яблуко де-небудь згадує чи використовує цей підхід? Бо якщо ні, майбутній випуск iOS може змінити динамічні властивості, залежачи від MOC, і порушити цей підхід. Яблучні документи з цим незрозумілі: вони наголошують на важливості контексту та призначеного ініціалізатора, але в документі MO є одна згадка про те, що "якщо контекст не нульовий, то ...", що припускає, що нуль може бути нормальним
Ревінь

41
Я використовував цей підхід деякий час тому, але почав бачити дивну поведінку та збої, коли я модифікував ці об'єкти та / або створював для них зв'язки, перш ніж вставляти їх у MOC. Я обговорив це з інженером Core Data у WWDC, і він сказав, що хоча API для неасоційованих об'єктів є, він настійно рекомендує не використовувати його, оскільки MOC сильно покладається на сповіщення KVO, надіслані його об'єктами. Він запропонував використовувати звичайний об'єкт NSO для тимчасових об'єктів, оскільки це набагато безпечніше.
Адріан Шеніг,

7
Здається, це не працює добре з iOS 8, особливо з постійними стосунками. Хтось ще може це підтвердити?
Janum Trivedi

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;

Зверніть увагу, що збереження дочірнього контексту застосовує зміни до батьківського контексту. Збереження батьківського контексту зберігає зміни.

Див WWDC 2012 сесій 214 для повного пояснення.


1
Дякуємо, що запропонували це! Я написав демонстраційне тестування цього методу порівняно з використанням контексту nil і принаймні на OSX, це спрацювало під час вставки контексту nil, втративши свої атрибути при збереженні - демонстрація на github.com/seltzered/CoreDataMagicRecordTempObjectsDemo
Vivek Gani

Що є mocв третьому фрагменті? Це childContextчи myMangedObjectContext?
bugloaf

Це дитинаКонтекст
залізничний парад

це рішення краще, ніж наявність нульового контексту.
Will Y

Оскільки NSManagedObjectвже надається відповідне NSManagedObjectContext, ви можете автоматизувати вибір контексту: NSManagedObject* objectRelatedContextually = [objectWithRelationship.managedObjectContext objectWithID:objectRelated.objectID];а потім objectWithRelationship.relationship = objectRelatedContextually;.
Gary

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

Якщо ви спробуєте використовувати a NSManagedObjectу неправильному контексті, ви отримаєте винятки під час збереження.


Створення другого контексту саме для цього дуже марнотратне, оскільки стояння a NSManagedObjectContextє дорогим як в пам'яті, так і в процесорі. Я розумію, що це було спочатку в деяких прикладах Apple, але вони оновили та виправили ці приклади.
Marcus S. Zarra,

2
Apple досі використовує цю техніку (створення другого контексту керованого об’єкта) для прикладу коду CoreDataBooks.
неван-король

1
Примітка. Apple оновила CoreDataBooks, справді він все ще використовує два контексти, але зараз 2-й контекст є дочірнім для першого. Цей прийом обговорюється (і рекомендується) у презентації 303 WWDC 2011 (що нового в Основних даних в iOS) і згадується тут (з набагато простішим кодом для злиття змін вгору) stackoverflow.com/questions/9791469/…
Ревінь

4
"Створення другого контексту саме для цього дуже марнотратне, оскільки встановлення NSManagedObjectContext є дорогим як для пам'яті, так і для центрального процесора." . Ні це не так. Постійні залежності координатора магазину (керована об’єктна модель та конкретні сховища) є не контекстом. Контексти легкі.
втамований

3
@quellish Погодився. У своїх нещодавніх переговорах щодо продуктивності основних даних на WWDC Apple заявила, що створення контекстів дуже легке.
Джессі

9

Створення тимчасових об'єктів з контексту nil працює нормально, поки ви насправді не спробуєте встановити зв'язок з об'єктом, контекст якого! = Nil!

переконайтеся, що з вами все в порядку.


Мені це не подобається
Чарлі

8

Те, що ви описуєте, саме для чого NSManagedObjectContext.

З Посібника з програмування основних даних: Основи основних даних

Ви можете уявити контекст керованого об'єкта як розумну подряпину. Коли ви отримуєте об’єкти з постійного сховища, ви приносите тимчасові копії на скретч-майданчик, де вони утворюють графік об’єктів (або колекцію графіків об’єктів). Потім ви можете змінювати ці об’єкти, як вам подобається. Однак якщо ви насправді не збережете ці зміни, постійне сховище залишається незмінним.

І Посібник із програмування основних даних: Перевірка керованих об’єктів

Це також лежить в основі ідеї контексту керованого об'єкта, що представляє "скретч-панель" - загалом, ви можете перенести керовані об'єкти на скретч-панель і редагувати їх, як завгодно, перш ніж в кінцевому підсумку або внести зміни, або відкинути їх.

NSManagedObjectContexts розроблені для полегшення ваги. Ви можете створювати та відкидати їх за бажанням - це постійний координатор магазинів і "залежності", які "важкі". Один постійний координатор магазину може мати багато контекстів, пов’язаних з ним. За старою, застарілою моделлю обмеження потоків це означало б встановлення однакового постійного координатора сховища для кожного контексту. Сьогодні це означало б підключення вкладених контекстів до кореневого контексту, який пов'язаний з постійним координатором сховища.

Створюйте контекст, створюйте та змінюйте керовані об'єкти в цьому контексті. Якщо ви хочете зберегти їх і повідомити про ці зміни, збережіть контекст. В іншому випадку викиньте його.

Спроба створити керовані об’єкти, незалежні від NSManagedObjectContext, вимагає неприємностей. Пам'ятайте, що Основні дані - це врешті-решт механізм відстеження змін для графіку об'єкта. Через це керовані об'єкти насправді є частиною контексту керованого об'єкта . Контекст відстежує їх життєвий цикл , і без контексту не вся функціональність керованого об’єкта працюватиме коректно.


6

Залежно від того, як ви використовуєте тимчасовий предмет, є деякі застереження щодо вищезазначених рекомендацій. Мій варіант використання полягає в тому, що я хочу створити тимчасовий об'єкт і прив'язати його до подань. Коли користувач вирішує зберегти цей об’єкт, я хочу налаштувати відносини до існуючих об’єктів і зберегти. Я хочу зробити це, щоб уникнути створення тимчасового об'єкта для зберігання цих значень. (Так, я міг би просто почекати, поки користувач збереже, а потім захопити вміст подання, але я поміщаю ці подання всередину таблиці, і логіка для цього менш елегантна.)

Варіанти тимчасових об'єктів:

1) (бажано) Створити тимчасовий об'єкт у дочірньому контексті. Це не буде працювати, оскільки я прив’язую об’єкт до інтерфейсу користувача, і я не можу гарантувати, що доступ до об’єкта викликається у дочірньому контексті. (Я не знайшов жодної документації, яка б свідчила про інше, тому я повинен припустити.)

2) Створіть тимчасовий об'єкт з нульовим контекстом об'єкта. Це не працює і призводить до втрати / пошкодження даних.

Моє рішення: Я вирішив це, створивши тимчасовий об’єкт із нульовим контекстом об’єкта, але коли я зберігаю об’єкт, а не вставляю його як номер 2, я копіюю всі його атрибути в новий об’єкт, який я створюю в основному контексті. Я створив допоміжний метод у своєму підкласі NSManagedObject, який називається cloneInto: це дозволяє мені легко копіювати атрибути та відносини для будь-якого об’єкта.


Це те, що я шукаю. Але я сумніваюся, як ви будете поводитися з атрибутами стосунків?
Мані

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, оскільки всі подібні запитання для 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")
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.