开发者

Core-Data willSave: method

开发者 https://www.devze.com 2023-02-08 06:26 出处:网络
I have an attribute modificationDate in my Entity A. I want to set its value whenever NSManagedObject is saved. However, if i try to do that in NSManagedObject willSave: method, i get an error:

I have an attribute modificationDate in my Entity A. I want to set its value whenever NSManagedObject is saved. However, if i try to do that in NSManagedObject willSave: method, i get an error:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '开发者_StackOverflowFailed 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.' ***

So, i'm wondering, what's the best way to set the value of modificationDate?


In fact the apple docs (which are only half read in the accepted answer) don't recommend this method. They explicitly say you should use NSManagedObjectContextWillSaveNotification. An example might be:

@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

I use this (with a few other methods: primary key for example) as an abstract base class for most core data projects.


From the NSManagedObject docs for willSave:

If you want to update a persistent property value, you should typically test for equality of any new value with the existing value before making a change. If you change property values using standard accessor methods, Core Data will observe the resultant change notification and so invoke willSave again before saving the object’s managed object context. If you continue to modify a value in willSave, willSave will continue to be called until your program crashes.

For example, if you set a last-modified timestamp, you should check whether either you previously set it in the same save operation, or that the existing timestamp is not less than a small delta from the current time. Typically it’s better to calculate the timestamp once for all the objects being saved (for example, in response to an NSManagedObjectContextWillSaveNotification).

So maybe something along the lines of:

-(void)willSave {
    NSDate *now = [NSDate date];
    if (self.modificationDate == nil || [now timeIntervalSinceDate:self.modificationDate] > 1.0) {
        self.modificationDate = now;
    }
}

Where you can adjust the 1.0 to reflect the minimum delta between your expected save requests.


Actually a much better way than the accepted answer would be to use primitive accessors, as suggested in NSManagedObject's Documentation

`

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

`

Also, check whether the object is marked for deletion with -isDeleted, as -willSave gets called for those too.


There are obviously several good solutions to this question already, but I wanted to throw out a new one that worked best for one particular scenario I encountered.

(In Swift:)

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

    super.willSave()
}

The reason I needed this is because I have the peculiar requirement of needing to sometimes set the modificationDate manually. (The reason I sometimes set the time stamp manually is because I try to keep it in sync with a time stamp on the server.)

This solution:

  1. Prevents the infinite willSave() loop because once the time stamp is set, it will appear in changedValues()
  2. Doesn't require using observation
  3. Allows for setting the time stamp manually


Swift 4 solution which is a combination of zmit and Richard answer without the need of recurring to NSNotification:

override func willSave() {
    let expectedNewValue = "Your new value"
    if customField != expectedNewValue, changedValues()[#keyPath(Entity.customField)] == nil, !isDeleted {
        customField = expectedNewValue
    }
    super.willSave()
}
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号