Core-Data willSave: метод - PullRequest
       24

Core-Data willSave: метод

21 голосов
/ 02 февраля 2011

У меня есть атрибут 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 ]

47 голосов
/ 23 мая 2012

На самом деле 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

Я использую это (с несколькими другими методами: например, с первичным ключом) в качестве абстрактного базового класса для большинства основных проектов данных.

32 голосов
/ 17 марта 2011

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

9 голосов
/ 24 февраля 2014

На самом деле гораздо лучшим способом, чем принятый ответ, было бы использование примитивных средств доступа, как предложено в Документация NSManagedObject

`

- (void)willSave
{
    if (![self isDeleted])
    {
        [self setPrimitiveValue:[NSDate date] forKey:@"updatedAt"];
    }
    [super willSave];
}

`

Кроме того, проверьте, помечен ли объект для удаления с помощью -isDeleted, поскольку -willSave также вызывается для них.

6 голосов
/ 05 сентября 2014

Очевидно, что у этого вопроса уже есть несколько хороших решений, но я хотел выпустить новое, которое лучше всего подошло бы для одного конкретного сценария, с которым я столкнулся.

(In Swift:)

override func willSave() {
    if self.changedValues()["modificationDate"] == nil {
        self.modificationDate = NSDate()
    }

    super.willSave()
}

Причина, по которой я нуждался в этом, заключается в том, что у меня есть своеобразное требование необходимости иногда устанавливать дату модификации вручную . (Причина, по которой я иногда устанавливаю метку времени вручную, заключается в том, что я пытаюсь синхронизировать ее с меткой времени на сервере.)

Это решение:

  1. Предотвращает бесконечный цикл willSave (), поскольку после установки метки времени она будет отображаться в changeValues ​​()
  2. Не требует использования наблюдения
  3. Позволяет установить отметку времени вручную
0 голосов
/ 13 мая 2018

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