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?


person Mustafa    schedule 02.02.2011    source источник


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

person CalloRico    schedule 17.03.2011
comment
вам также нужно проверить, находится ли модификационная дата в измененных значениях - person malhal; 02.09.2015

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

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

person Paul de Lange    schedule 23.05.2012
comment
Мне нравиться! Просто искал что-то подобное - оцените пример кода. Нужно ли мне звонить [load super]? (Подозреваю, что нет, если NSManagedObject или его родитель ничего с ним не делают.) - person Joe D'Andrea; 23.05.2012
comment
OK! Я попробовал это, и это кажется слишком усердным (или, что более вероятно, я неправильно его использую). У меня есть управляемый объект, который использует lastModified (через MYAPPTrackedManagedObject, в основном TrackedEntity выше). Однако этот объект содержит связи с другими объектами, которые не используют lastModified. Таким образом, setLastModified: не будет распознан этими другими объектами, и возникнет исключение. Может быть, мне нужно как-то набрать его обратно? - person Joe D'Andrea; 23.05.2012
comment
Ааа, может быть, просто пройти allModified и отфильтровать все, что не отвечает на setLastModified:, и использовать это. - person Joe D'Andrea; 23.05.2012
comment
Я отредактировал ответ, чтобы включить фильтр для вас. Я не звоню [load super] и у меня нет (заметных) проблем, хотя это не может сильно повредить... не так ли? :) - person Paul de Lange; 24.05.2012
comment
Почему вы делаете это в +load, а не +initialize? - person MJN; 27.10.2013
comment
+load вызывается, когда среда выполнения загружает класс, то есть: гарантированно вызывается до того, как будет выполнено обновление контекста. +initialize вызывается при первом доступе к классу. Просто чтобы убедиться, что все уведомления перехвачены, я использую +load. - person Paul de Lange; 28.10.2013
comment
Я пытался понять это, но, кажется, не могу; Почему вы использовали @autoreleasepool? любой совет? - person Gagan Singh; 11.06.2014
comment
load вызывается, когда класс добавляется в среду выполнения ObjC. Обычно это перед всеобъемлющим @autoreleasepool, который находится в файле main.m. Если у вас нет настройки autoreleasepool, объекты будут просачиваться. - person Paul de Lange; 12.06.2014
comment
так что NSManagedObjectContextWillSaveNotification происходит перед любой проверкой объекта? это то, что я нашел неясным из документов - person Sam; 25.09.2014
comment
Разве предикат не должен быть [NSPredicate predicateWithFormat: @"self isKindOfClass: %@", self], потому что здесь self равно TrackedEntity? - person Benjohn; 16.07.2015

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

`

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

`

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

person zmit    schedule 24.02.2014
comment
В документах упоминается о переопределении willSave: если вы изменяете значения свойств с помощью примитивных методов доступа, вы избегаете возможности бесконечной рекурсии, но Core Data не заметит внесенных вами изменений. - person Pietro Rea; 14.05.2014
comment
@PietroRea это будет сохранено в базе данных? - person Ben Affleck; 13.09.2015

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

(В Свифте :)

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

    super.willSave()
}

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

Это решение:

  1. Предотвращает бесконечный цикл willSave(), потому что как только отметка времени установлена, она появится в changeValues()
  2. Не требует наблюдения
  3. Позволяет установить отметку времени вручную
person Richard Venable    schedule 05.09.2014
comment
интересно. мы пытаемся избежать использования каких-либо временных меток клиента для чего-либо критического на сервере. часы устройства ненадежны, могут вызывать ошибки в алгоритмах синхронизации. (обычно значение lastSynced сохраняется на клиенте и используется для запроса на сервер только тех объектов, которые изменились). - person Sam; 25.09.2014
comment
@Sam, я использую это для данных, в которых пользователь, редактирующий их, имеет полномочия разрешать конфликты слияния. Хотя я не предоставляю пользовательский интерфейс для разрешения, поэтому я просто использую метки времени для автоматического разрешения конфликтов. Поскольку пользователь имеет право на эти данные, меня не беспокоит, что его часы могут быть неправильными. Это может беспокоить моего пользователя, но они, вероятно, также будут обеспокоены тем, что я пропущу их встречи и тому подобное :). Но ваша точка зрения верна, и я не думаю, что стал бы использовать этот метод для общедоступных данных. - person Richard Venable; 26.09.2014

Решение 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()
}
person matsoftware    schedule 13.05.2018