Get modification date for NSManagedObject in Core Data?
Asked Answered
P

7

18

Outside of adding an NSDate property to each Entity in my Core Data store, is there a programmatic way to get the modification date for any object?

Packhorse answered 28/4, 2011 at 3:34 Comment(0)
G
24

No, you must add a date and manage it yourself. You can use override -willSave in your managed object to update the time stamp, but read the API documentation for NSManagedObject on -willSave for how to update without causing a willSave loop (the docs even talk about the case of updating a timestamp). The docs also mention using the NSManagedObjectContextWillSaveNotification, but that may be more trouble to set up than a simple check to not set the timestamp too quickly.

Grassi answered 28/4, 2011 at 3:42 Comment(2)
Apple gives examples on how to do this. Read about -willSave: developer.apple.com/library/mac/#documentation/Cocoa/Reference/…Mariomariology
Here is an updated link: developer.apple.com/documentation/coredata/nsmanagedobject/…Loading
T
13

I personally check if updatedAt was modified, and if yes then I don't touch it anymore. This way I break the willSave loop.

- (void)awakeFromInsert {
    [super awakeFromInsert];

    self.primitiveUpdatedAt = [NSDate date];
}

- (void)willSave {
    [super willSave];

    if(![self isDeleted] && self.changedValues[@"updatedAt"] == nil) {
        self.updatedAt = [NSDate date];
    }
}
Tonyatonye answered 30/10, 2015 at 12:4 Comment(0)
B
12

At WWDC19 it was announced Derived Attributes.

An updated attribute like this one is updated automatically by Core Data:

Core Derived Attribute screenshot

Note: this may produce an invalid Mapping Model.

Byssinosis answered 11/6, 2020 at 15:33 Comment(2)
Good news! Thanks a lot!Clearness
Note that this will not update "updated" in the saved NSManagedObject context until the object is explicitly refreshed from the store. Which is a shame.Pendragon
S
7

Please note this solution assume we have a property called dateUpated in model .

Instead of handling this at individual objects. I would handle this through a notification. Apple documentation also suggests this way as well.

1. Register for NSManagedObjectContextWillSaveNotification notification

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(willSaveContext:)         
                                             name:NSManagedObjectContextWillSaveNotification 
                                           object:nil];

2 Set the property for updatedDate at observer method for each updated object.

- (void)willSaveContext:(NSNotification *)notification{

    NSManagedObjectContext *context = [notification object];
    NSSet *updatedObject = [context updatedObjects];

    for (NSManagedObject *managedObject in [updatedObject allObjects]) {
        if ([[managedObject.entity propertiesByName] objectForKey:@"dateUpdated"]) {
            [managedObject setValue:[NSDate date] forKey:@"dateUpdated"];
        }

    }

}
Synecdoche answered 4/3, 2015 at 15:36 Comment(0)
F
2

I found this Q/A helpful in getting started on setting up an attribute for date modified in Core Data. In the process, I arrived at a couple of tips that might also help:

(Tip 1: avoiding willSave recursion)

  • Another way to avoid a willSave loop is to write a custom routine for saving the context that iterates through its updatedObjects looking for those that have a dateModified property and setting it. Do the actual calls to commitEditing and save after that loop. Don't bother with willSave at all.

(Tip 2: live updating — but won’t work with undo)

  • If you need live updating to display the date modified to the user in a table, you can also set the dateModified in your switch-case (or whatever) for the date-modified column in your objectValueForTableColumn delegate method (willDisplayCell for iOS).

  • There, test if the object for that row isUpdated and, if so, set the dateModified. I doubt that the if (obj isUpdated) check is very expensive. But be sure to do this only for the relevant column so as not to repeat it unnecessarily.

  • Return whatever string representation you are using for the column. To avoid obvious disparities between the time of the actual modification and the date set, show only the date, not the time.

  • This will cause dateModified to be updated whenever the user makes a table selection, as well as when the table is reloaded. Which is not perfect -- if user modifies an attribute that is not represented in the table, the column won't update until they make a selection. But it's reasonably responsive and a lot easier than implementing an extensive KVO scheme.

  • (You will still want to set the date in the save routine, to catch modifications which are invisible to the table.)

  • UNDO COMPLICATION: Unfortunately, the undo manager will consider the table delegate method’s change to dateModifed as an event unto itself. Subsequently calling undo simply undoes the last change to dateModified — over and over again. I tried overcoming this by adding a tracker and a check for a non-empty changedValues dictionary to ensure that the dateModified got set only once pre-save. That worked for undoing deletions, but not for regular edits. So there’s no quick way of accomplishing live updating of dateModified pre-save.

Fob answered 18/12, 2011 at 21:51 Comment(1)
Tip 0: Apple tells how to avoid willSave recursion in the documentation. developer.apple.com/library/mac/#documentation/Cocoa/Reference/…Mariomariology
P
1

A Swift version of Ben Affleck's answer

public override func willSave() {
    super.willSave()
        
    if !isDeleted, changedValues()["updatedAt"] == nil {
        self.setValue(Date(), forKey: "updatedAt")
    }
}
Prosperous answered 5/4, 2021 at 17:13 Comment(0)
L
0

If anyone wants an easy way of doing this in Swift: I wrote a blog post about a simple UpdateListener class to update all the updateDate and insertDate properties. You can find the entire class in this gist. All you have to do is call UpdateListener.setupSharedInstance() in the application(:didFinishLaunchingWithOptions:) method of your application delegate.

Lafountain answered 3/9, 2015 at 8:38 Comment(1)
Website unavailable.Conchita

© 2022 - 2024 — McMap. All rights reserved.