NSFileVersion.currentVersionOfItem not consistent across devices after simultaneous edit

I’m building an app that edits files in iCloud and uses an NSFilePresenter to monitor changes. When a conflict occurs, the system calls presentedItemDidGain(_:).
In that method, I merge the versions by reading the current (canonical) version using NSFileVersion.currentVersionOfItem(at:) and the conflicting ones using NSFileVersion.unresolvedConflictVersionsOfItem(at:).

This generally works, but sometimes, if two devices edit the same file at the same time, each device sees its own local version as the current one. For example:

Device A writes fileVerA (slightly later in real time)
Device B writes fileVerB

On Device A all works fine, currentVersionOfItem returns fileVerA, as expected, and unresolvedConflictVersionsOfItem returns [fileVerB].

But on Device B, currentVersionOfItem returns fileVerB!? And unresolvedConflictVersionsOfItem returns the same, local file [fileVerB], without any hint of the other conflicting version, fileVerA.
Later, the newer version from the Device A arrives on Device B as a normal, non-conflicting update via presentedItemDidChange(_:).

This seems to contradict Apple’s documentation: “The currentVersionOfItemAtURL: method returns an NSFileVersion object representing what’s referred to as the current file; the current file is chosen by iCloud on some basis as the current “conflict winner” and is the same across all devices.”

Is this expected behavior, or a bug in how iCloud reports file versions?

To be very clear, when you see currentVersionOfItem returning fileVerB on device B, does otherVersionsOfItem(at:) return fileVerA? I am trying to confirm if fileVerA has been synchronized to device B at the moment.

Also, after the newer version from the Device A arrives on Device B, does currentVersionOfItem still return fileVerB on device B?

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

On device B otherVersionsOfItem(at:) returns [fileVerB], no sign of fileVerA at this point. It seems that fileVerA is not yet synchronized to device B.
However, I expect that on device B, the system should not call NSFilePresenter’s presentedItemDidGain(_:) before the file from device A has been downloaded, or at least before the fileVerA URL has been created on disk (so that I can then read its contents using NSFileCoordinator).

In presentedItemDidGain I check if the URL passed in is in a conflict state by using 'newVersionURL.isConflict', and it returns true, and then I run the merge. Is this the right way to check for a pending conflict?

Also, after the newer version from the Device A arrives on Device B, does currentVersionOfItem still return fileVerB on device B?

I no longer check for currentVersionOfItem because the system calls presentedItemDidChange (a split second after presentedItemDidGain), and in this method, I assume there’s no conflict, so I don’t run the merge algorithm. Instead, I simply read the file contents, which happen to be fileVerA, and redisplay it.

I noticed that in case of conflict, the system calls presentedItemDidChange then shortly after presentedItemDidGain. So in presentedItemDidChange I always check for conflict using unresolvedConflictVersionsOfItem(at:), and bail out if positive, because I expect to receive a presentedItemDidGain next.
When I receive it, I run the merge then write the resulting file on disk. The system then calls again presentedItemDidChange, I check again for conflict, this time is negative, and I proceed to display the merged file.

I tried to reproduce this, and call 'currentVersionOfItem', but I can’t trigger the case right now, it only happens occasionally.

fileVerA not existing on device B explains why currentVersionOfItem returns fileVerB. But as you've pointed out and the documentation mentions , presentedItemDidGain(_:) is supposed to be called after a new version was added, which does seem to conflict with your observation. I’d hence suggest that you file a feedback report – If you do so, please share your report ID here.

In presentedItemDidGain I check if the URL passed in is in a conflict state by using 'newVersionURL.isConflict', and it returns true, and then I run the merge. Is this the right way to check for a pending conflict?

Yes, the above flow sounds right, assuming presentedItemDidGain is called at the documented timing.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

NSFileVersion.currentVersionOfItem not consistent across devices after simultaneous edit
 
 
Q