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 вручную. (Причина, по которой я иногда устанавливаю отметку времени вручную, заключается в том, что я пытаюсь синхронизировать ее с отметкой времени на сервер.)

такое решение:

  1. предотвращает бесконечный цикл willSave (), потому что после установки отметки времени он появится в changedValues ()
  2. не требует использования наблюдения
  3. позволяет установить отметку времени вручную

решение 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()
}