It may take some time for the request to retrieve metadata from iCloud. The didFinishGathering command may initially contain only those results that the device already knows about, and not changes that it was not possible to learn about iCloud about.
Instead of stopping and starting NSMetadataQuery, it would be preferable to install it and continue listening, also registering for:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(iCloudQueryDidUpdate:) name:NSMetadataQueryDidUpdateNotification object:iCloudQuery];
... and retrieve updates when they come in. Therefore, you also need to change the finishGathering method, rather than stopping the request and enable Updates at the end.
You will have to reconsider your approach a bit so that the fact that the first set of results will not necessarily still be known. More normally, NSMetadataQuery is used to monitor iCloud, expecting that changes generated by other devices can appear at any time - not only when the application starts.
If you need to make sure that you have the most up-to-date metadata for iCloud, the only approach I have found reliable (both on iOS 5 and iOS 6) is to enter a small file in iCloud (usually using an excellent name form and named with a UUID, so it is guaranteed to be unique), and then in the iCloudQueryDidUpdate method: not counting the results of the request, which will be completed until this file is returned by the request, and the metadata that it is also loaded into iCloud. Once you return, you can be sure that you have received the latest metadata from iCloud.
Check download in iCloudQueryDidUpdate: using:
int resultCount = [iCloudQuery resultCount]; for (int i = 0; i < resultCount; i++) { NSMetadataItem *item = [iCloudQuery resultAtIndex:i]; BOOL isUploaded = [[item valueForAttribute:NSMetadataUbiquitousItemIsUploadedKey] boolValue]; BOOL isDownloaded = [[item valueForAttribute:NSMetadataUbiquitousItemIsDownloadedKey] boolValue]; NSURL *url = [item valueForAttribute:NSMetadataItemURLKey]; BOOL documentExists = [[NSFileManager defaultManager] fileExistsAtPath:[url path]];
And do not forget to delete the injected file when you are done with it, otherwise they will be mounted every time your application starts.
Edit:
The way I implemented these checks had a built-in delay that, as soon as I took it out, I found a case where the above was not completely reliable.
I deleted the metadata elements (deleted using "Settings / iCloud / Storage" and "Backup / Storage management until the current launch") that were sent, downloaded and uploaded and saved to disk before the full metadata returned for my file attachment . However, after the injected file was registered as downloaded, downloaded and existing locally on the disk, one of these deleted files was still listed in the metadata, downloaded and downloaded - BUT NOT existing on the disk.
Be that as it may, the iCloud daemon hears about a pending deletion from the old data and actually performs the deletion before the metadata that your application sees has been updated to reflect this. Crazy, huh? Thus, I have to update my recommendation above to only consider the results of the query if the item is reported as loaded, loaded AND it exists in the local folder using the [NSFileManager fileExistsAtPath:] method. Code edited above to reflect this.
After that, all you can do is stick to something like a delay of 1 second before acting on the results of the query, to be absolutely sure that all the metadata has been received - although this is something that I DO NOT NEED to do, Holding false time delays in the code to make it work, it feels too close to black magic for me. And itβs significant that you really donβt understand what is happening - although without extra hooks to processing for iCloud, what else do we do?