iOS CoreData+MoGenerator: как инициализировать управляемый объект только один раз, когда я использую вложенные контексты?
Я использую mogenerator для генерации кода из модели с управляемым объектом TestPerson. TestPerson наследуется от абстрактного объекта TLSyncParent. В TLSyncParent у меня есть код:
- (void) awakeFromInsert
{
[super awakeFromInsert];
QNSLOG(@"%@\n%@", self.managedObjectContext, self.description);
if (self.syncStatus == nil) {
self.syncStatusValue = SYNCSTATUS_NEW;
self.tempObjectPID = [self generateUUID];
QNSLOG(@"After init values\n%@", self.description);
}
}
Я создаю объект TestPerson в childMOC, родитель которого - mainMOC, родитель которого - rootMOC. awakeFromInsert работает как положено и вносит изменения в init. Когда я сохраняю childMOC в mainMOC, awakeFromInsert запускается снова. От документов я бы этого не ожидал, но есть некоторая двусмысленность. Из документов: "Обычно этот метод используется для инициализации специальных значений свойств по умолчанию. Этот метод вызывается только один раз за время существования объекта". Реальная проблема заключается в том, что когда awakeFromInsert запускается в mainMOC, изменения init, сделанные в childMOC, НЕ присутствуют. awakeFromInsert, очевидно, запускается до того, как на самом деле произойдет сохранение.
2013-10-02 11:22:45.510_xctest[21631:303] TestPerson -awakeFromInsert <NSManagedObjectContext: 0xd684780>
<TestPerson: 0xd6863b0> (entity: TestPerson; id: 0xd684ed0 <x-coredata:///TestPerson/t02B71E0D-AE3F-4605-8AC7-638AE072F2302> ; data: {
dept = nil;
job = nil;
objectPID = nil;
personName = nil;
syncStatus = 0;
tempObjectPID = nil;
updatedAt = nil;
})
2013-10-02 11:22:45.511_xctest[21631:303] TestPerson -awakeFromInsert After init values
<TestPerson: 0xd6863b0> (entity: TestPerson; id: 0xd684ed0 <x-coredata:///TestPerson/t02B71E0D-AE3F-4605-8AC7-638AE072F2302> ; data: {
dept = nil;
job = nil;
objectPID = nil;
personName = nil;
syncStatus = 4;
tempObjectPID = "7AB46623-C597-4167-B189-E3AAD24954DE";
updatedAt = nil;
})
2013-10-02 11:22:45.511_xctest[21631:303] CoreDataController -saveChildContext: Saving Child MOC
2013-10-02 11:22:45.511_xctest[21631:303] TestPerson -awakeFromInsert <NSManagedObjectContext: 0xd682180>
<TestPerson: 0xd68fce0> (entity: TestPerson; id: 0xd684ed0 <x-coredata:///TestPerson/t02B71E0D-AE3F-4605-8AC7-638AE072F2302> ; data: {
dept = nil;
job = nil;
objectPID = nil;
personName = nil;
syncStatus = 0;
tempObjectPID = nil;
updatedAt = nil;
})
2013-10-02 11:22:45.511_xctest[21631:303] TestPerson -awakeFromInsert After init values
<TestPerson: 0xd68fce0> (entity: TestPerson; id: 0xd684ed0 <x-coredata:///TestPerson/t02B71E0D-AE3F-4605-8AC7-638AE072F2302> ; data: {
dept = nil;
job = nil;
objectPID = nil;
personName = nil;
syncStatus = 4;
tempObjectPID = "B799AFDA-3514-445F-BB6F-E4FE836C4F9D";
updatedAt = nil;
})
Как правильно инициализировать управляемый объект при использовании структуры MoGenerator?
3 ответа
Хорошо, благодаря Тому Херрингтону, я нашел очень хороший способ сделать это. Кажется, я делаю то, что хочу, с минимумом хлопот. Он идеально вписывается в структуру MoGenerator. У меня уже была категория на NSManagedObject с методом initWithMOC. Я добавил вызов метода awakeFromCreate и предоставил реализацию по умолчанию. Вы просто переопределяете awakeFromCreate так же, как переопределяете awakeFromInsert. Единственным требованием является то, что вы ВСЕГДА создаете МО, используя метод initWithMOC.
@implementation NSManagedObject (CoreDataController)
+ (NSManagedObject*) initWithMOC: (NSManagedObjectContext*) context
{
NSManagedObject* mo = (NSManagedObject*)
[NSEntityDescription insertNewObjectForEntityForName: NSStringFromClass(self)
inManagedObjectContext: context];
[mo awakeFromCreate];
return mo;
}
- (void) awakeFromCreate
{
return;
}
Документация по awakeFromInsert
является несколько устаревшим и не отражает реальность вложенных контекстов. Когда он говорит, что метод
Вызывается автоматически средой базовых данных, когда получатель впервые вставляется в контекст управляемого объекта.
Он должен действительно сказать что-то вроде "..first вставлено в любой контекст управляемого объекта", поскольку (как вы обнаружили) это происходит более одного раза с вложенными контекстами. На самом деле, понятие awakeFromInsert
является устаревшим при использовании вложенных контекстов. Метод был четко разработан в старые не вложенные времена и не адаптировался.
Есть несколько способов справиться с этим. Одним из них является простая проверка во время выполнения, где вы делаете что-то вроде:
if ([[self managedObjectContext] parentContext] != nil) {
// Set default values here
}
Этот код выполняется только тогда, когда текущий контекст является дочерним по отношению к некоторому другому контексту. Метод по-прежнему выполняется для родительского контекста, но вы пропускаете установщики значений по умолчанию. Это нормально, если вы когда-либо вкладываете только один уровень в глубину, то есть один родитель с одним или несколькими дочерними контекстами, но без родительского контекста. Если вы когда-нибудь добавите еще один уровень вложенности, вы вернетесь туда, откуда начали.
Другой вариант (и тот, который я обычно предпочитаю) - это перенести код значения по умолчанию в отдельный метод, а затем не использовать awakeFromInsert
совсем. То есть создать метод с именем что-то вроде setDefaultValues
, который в вашем случае устанавливает значения для syncStatusValue
а также tempObjectPID
, Вызовите этот метод сразу после того, как вы сначала создадите новый экземпляр, и больше нигде. Так как он никогда не получает автоматический вызов, код никогда не запускается, за исключением случаев, когда вы указываете ему на выполнение.
Я почти уверен, что Mogenerator не меняет способ создания управляемых объектов, а только перемещает фактические классы управляемых объектов в файлы, сгенерированные машиной, с префиксом "_", и создает подклассы этих управляемых объектов, чтобы поместить всю пользовательскую логику в чтобы он не терялся при обновлении классов управляемых объектов.