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
?
5 ответов
из документов 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, чтобы отразить минимальную дельту между ожидаемыми запросами сохранения.
в самом деле яблочный документов (которые читаются только наполовину в принятом ответе) не рекомендуют этот метод. Они явно говорят, что вы должны использовать 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
Я использую это (с несколькими другими методами: первичный ключ, например) в качестве абстрактного базового класса для большинства проектов основных данных.
на самом деле гораздо лучший способ, чем принятый ответ, - использовать примитивные аксессоры, как предложено в документация NSManagedObject
`
- (void)willSave
{
if (![self isDeleted])
{
[self setPrimitiveValue:[NSDate date] forKey:@"updatedAt"];
}
[super willSave];
}
`
кроме того, проверьте, помечен ли объект для удаления с помощью -isDeleted
, as -willSave
также вызывается для них.
очевидно, что уже есть несколько хороших решений этого вопроса, но я хотел выбросить новый, который лучше всего работал для одного конкретного сценария, с которым я столкнулся.
(In Swift:)
override func willSave() {
if self.changedValues()["modificationDate"] == nil {
self.modificationDate = NSDate()
}
super.willSave()
}
причина, по которой мне это нужно, потому что у меня есть своеобразный требование необходимости иногда устанавливать modificationDate вручную. (Причина, по которой я иногда устанавливаю отметку времени вручную, заключается в том, что я пытаюсь синхронизировать ее с отметкой времени на сервер.)
такое решение:
- предотвращает бесконечный цикл willSave (), потому что после установки отметки времени он появится в changedValues ()
- не требует использования наблюдения
- позволяет установить отметку времени вручную
решение Swift 4, которое представляет собой комбинацию ответа zmit и Richard без необходимости повторения NSNotification
:
override func willSave() {
let expectedNewValue = "Your new value"
if customField != expectedNewValue, changedValues()[#keyPath(Entity.customField)] == nil, !isDeleted {
customField = expectedNewValue
}
super.willSave()
}