I have studied this problem further. I have determined what seems to be going on, and also workaround. NOTE this only applies to NSPersistentDocument
- without autosaving.
Firstly an important note on file timestamps and file system type. HFS+
timestamps have a resolution on one second. APFS
timestamps have a resolution of 1 nanosecond.
My problems only started to manifest itself when the OS X App's iCloud container was migrated to APFS
.
Here is a typical sequence (I've used OS X and iOS as example devices - but the same sequence happens regardless OS type for the 'other' iCloud connected device):
- Open a file on OS X in the iCloud app container.
- Make a change and save the change to the file, at this point the file modification date on
APFS
will have a fractional second component.
- Open the same file on another device - say iOS (after allowing a moment for iCloud to propagate the changes). The modification date on opening the file on iOS has the fractional seconds component truncated.
- Soon afterwards, back on OS X, the modification date of the recently saved file will have the fractional seconds component truncated (by iCloud processes beyond my control -
presentedItemDidChange
is called at this time).
- If another change is saved to the OS X file, the change warning "This document's file has been changed by another application since you opened or saved it." will appear. This is because of a mismatch between the
NSDocument
self.fileModificationDate
from the last save (which contains a fractional seconds component) and the file modification date (which has had the fractional seconds component truncated).
The implications are:
- when iCloud exchanges timestamp metadata between hosts, the resolution is in seconds.
- when a file is opened, and the timestamp metadata is conveyed to other devices, it is in truncated form. It is also written back to the file on the device that wrote the file last.
The workaround I have employed (which is only necessary because I am using NSPersistentDocument
without autoSaving) is to set self.fileModificationDate
to the fractional seconds truncated version (yes it is read-write), if and only if self.fileModificationDate
matches the file modification date ignoring fractional seconds. If this is done, the warning doesn't appear - and no harm is done (as the file had not been modified):
// saving file changes
NSDate *modDate = nil;
[self.fileURL getResourceValue:&modDate forKey:NSURLContentModificationDateKey error:NULL];
if (modDate && self.fileModificationDate) {
NSTimeInterval delta = [modDate timeIntervalSinceDate:self.fileModificationDate];
if (fabs(delta) > 0.0 && [modDate ss_isEqualToDateInSeconds:self.fileModificationDate])
self.fileModificationDate = modDate.ss_dateWithDateInSeconds;
}
[self saveDocumentWithDelegate:self
didSaveSelector:@selector(documentSaved:didSave:contextInfo:)
contextInfo:nil];
As I said at the outset — surprising behaviour. It would be nice if it was documented. Because my use of NSDocument
is non-standard, I don't know if others may have this problem.