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?
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.
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];
}
}
At WWDC19 it was announced Derived Attributes.
An updated
attribute like this one is updated automatically by Core Data:
Note: this may produce an invalid Mapping Model.
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"];
}
}
}
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
andsave
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.
A Swift version of Ben Affleck's answer
public override func willSave() {
super.willSave()
if !isDeleted, changedValues()["updatedAt"] == nil {
self.setValue(Date(), forKey: "updatedAt")
}
}
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.
© 2022 - 2024 — McMap. All rights reserved.