Core-Data willSave: метод
У меня есть атрибут modificationDate
в моем Entity A.
. Я хочу установить его значение, когда сохраняется NSManagedObject
. Однако, если я попытаюсь сделать это в методе NSManagedObject
willSave:
, я получаю сообщение об ошибке:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Failed to process pending changes before save. The context is still dirty after 100 attempts. Typically this recursive dirtying is caused by a bad validation method, -willSave, or notification handler.' ***
Итак, мне интересно, какой лучший способ установить значение modificationDate
?
Ответы
Ответ 1
Из документов NSManagedObject для willSave:
Если вы хотите обновить постоянное значение свойства, перед тем как внести изменения, вы должны, как правило, проверить равенство любого нового значения с существующим значением. Если вы измените значения свойств с помощью стандартных методов доступа, Core Data будет наблюдать результирующее уведомление об изменении и, таким образом, вызовет willSave еще раз перед сохранением контекста управляемого объекта объектов. Если вы продолжаете изменять значение в willSave, willSave будет продолжать вызываться до тех пор, пока ваша программа не выйдет из строя.
Например, если вы установите временную метку с последним изменением, вы должны проверить, была ли она ранее установлена в той же операции сохранения или что существующая временная метка не меньше, чем небольшая дельта от текущего времени. Как правило, лучше вычислить временную метку один раз для всех сохраняемых объектов (например, в ответ на NSManagedObjectContextWillSaveNotification).
Так что, может быть, что-то вроде:
-(void)willSave {
NSDate *now = [NSDate date];
if (self.modificationDate == nil || [now timeIntervalSinceDate:self.modificationDate] > 1.0) {
self.modificationDate = now;
}
}
Где вы можете настроить 1.0, чтобы отобразить минимальную дельту между ожидаемыми запросами на сохранение.
Ответ 2
Фактически apple docs (которые только наполовину читаются в принятом ответе) не рекомендуют этот метод. Они прямо говорят, что вы должны использовать NSManagedObjectContextWillSaveNotification. Примером может быть:
@interface TrackedEntity : NSManagedObject
@property (nonatomic, retain) NSDate* lastModified;
@end
@implementation TrackedEntity
@dynamic lastModified;
+ (void) load {
@autoreleasepool {
[[NSNotificationCenter defaultCenter] addObserver: (id)[self class]
selector: @selector(objectContextWillSave:)
name: NSManagedObjectContextWillSaveNotification
object: nil];
}
}
+ (void) objectContextWillSave: (NSNotification*) notification {
NSManagedObjectContext* context = [notification object];
NSSet* allModified = [context.insertedObjects setByAddingObjectsFromSet: context.updatedObjects];
NSPredicate* predicate = [NSPredicate predicateWithFormat: @"self isKindOfClass: %@", [self class]];
NSSet* modifiable = [allModified filteredSetUsingPredicate: predicate];
[modifiable makeObjectsPerformSelector: @selector(setLastModified:) withObject: [NSDate date]];
}
@end
Я использую это (с несколькими другими методами: например, первичный ключ) в качестве абстрактного базового класса для большинства основных проектов данных.
Ответ 3
На самом деле гораздо лучший способ, чем принятый ответ, - использовать примитивные аксессоры, как это предложено в Документация NSManagedObject
`
- (void)willSave
{
if (![self isDeleted])
{
[self setPrimitiveValue:[NSDate date] forKey:@"updatedAt"];
}
[super willSave];
}
`
Кроме того, проверьте, помечен ли объект для удаления с помощью -isDeleted
, поскольку для него также вызывается -willSave
.
Ответ 4
Уже есть несколько хороших решений для этого вопроса, но я хотел выбросить новый, который лучше всего работал для одного конкретного сценария, с которым я столкнулся.
(В Swift:)
override func willSave() {
if self.changedValues()["modificationDate"] == nil {
self.modificationDate = NSDate()
}
super.willSave()
}
Причина, по которой я нуждался в этом, - это то, что у меня есть своеобразное требование необходимости иногда устанавливать changeDate вручную. (Причина, по которой я иногда устанавливаю метку времени вручную, заключается в том, что я пытаюсь синхронизировать ее с меткой времени на сервере.)
Это решение:
- Предотвращает бесконечный цикл willSave(), поскольку после установки метки времени он будет отображаться в changedValues ()
- Не требует использования наблюдения
- Позволяет вручную установить отметку времени.