Подтвердить что ты не робот

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?

4b9b3361

Ответ 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 ​​()
  • Не требует использования наблюдения
  • Позволяет вручную установить отметку времени.