I have studied this problem further. I have determined what seems to be going on, and also workaround. The behaviour however is quite unexpected and surprising.
My problems only started to manifest itself when the OS X App's iCloud container was migrated to APFS. I also note that file timestamps on HFS+ have a resolution on one second, whilst APFS timestamps have a resolution of 1 nanosecond.
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, the timestamp metadata is conveyed to other devices, 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 which doesn't support 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.