Method for downloading iCloud files? Very confusing? Method for downloading iCloud files? Very confusing? ios ios

Method for downloading iCloud files? Very confusing?


Years later I am still experiencing periodic (though much more rare after some of the workarounds in other answers) issues downloading files. So, I contacted the developers at Apple asking for a technical review/discussion and here's what I found.

Periodically checking for the download status of the same NSURL, even if you're recreating it, is not the preferred method for checking the status. I don't know why it isn't - it seems like it should work, but it doesn't. Instead, once you begin to download the file, you should register an observer with NSNotificationCenter for the progress of that download, maintaining a reference to the query for that file. Here's the exact code sample that was provided to me. I've implemented it (with some app-specific tweaks) in my app, and it seems to be performing much more appropriately.

- (void)download:(NSURL *)url{    dispatch_queue_t q_default;    q_default = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);    dispatch_async(q_default, ^{        NSError *error = nil;        BOOL success = [[NSFileManager defaultManager] startDownloadingUbiquitousItemAtURL:url error:&error];        if (!success)        {            // failed to download        }        else        {            NSDictionary *attrs = [url resourceValuesForKeys:@[NSURLUbiquitousItemIsDownloadedKey] error:&error];            if (attrs != nil)            {                if ([[attrs objectForKey:NSURLUbiquitousItemIsDownloadedKey] boolValue])                {                    // already downloaded                }                else                {                    NSMetadataQuery *query = [[NSMetadataQuery alloc] init];                    [query setPredicate:[NSPredicate predicateWithFormat:@"%K > 0", NSMetadataUbiquitousItemPercentDownloadedKey]];                    [query setSearchScopes:@[url]]; // scope the search only on this item                    [query setValueListAttributes:@[NSMetadataUbiquitousItemPercentDownloadedKey, NSMetadataUbiquitousItemIsDownloadedKey]];                    _fileDownloadMonitorQuery = query;                    [[NSNotificationCenter defaultCenter] addObserver:self                                                             selector:@selector(liveUpdate:)                                                                 name:NSMetadataQueryDidUpdateNotification                                                               object:query];                    [self.fileDownloadMonitorQuery startQuery];                }            }        }    });}- (void)liveUpdate:(NSNotification *)notification{    NSMetadataQuery *query = [notification object];    if (query != self.fileDownloadMonitorQuery)        return; // it's not our query    if ([self.fileDownloadMonitorQuery resultCount] == 0)        return; // no items found    NSMetadataItem *item = [self.fileDownloadMonitorQuery resultAtIndex:0];    double progress = [[item valueForAttribute:NSMetadataUbiquitousItemPercentDownloadedKey] doubleValue];    NSLog(@"download progress = %f", progress);    // report download progress somehow..    if ([[item valueForAttribute:NSMetadataUbiquitousItemIsDownloadedKey] boolValue])    {        // finished downloading, stop the query        [query stopQuery];        _fileDownloadMonitorQuery = nil;    }}


This is a known issue. It's been reproduced here and here.

It seems adding a delay helps alleviate an unknown race condition, but no known workaround yet exists. From March 21st:

Anyway, I was wondering if you ever got past your main issue in this article? That is, over time, syncing seems to degrade and the import notifications stop arriving or are incomplete. You had identified that adding a delay in responding to the import notification had some value, but eventually proved to be unreliable.

And from the OP of the linked article:

The problem I had seemed to be caused by a race condition. Sometimes I would get the notification that my persistent store had been updated from iCloud--but the updated information would not be available yet. This seemed to happen about 1/4 of the time without the delay, and about 1/12th of the time with the delay.

It wasn't like the stability degraded...the system would always catch the update the next time I launched the app, and the auto conflict resolution fixed the problem. And then it would continue to function normally. But, eventually, it would drop another update.

...

At some level, I think we just need to trust that iCloud will eventually push out the information, and our conflict resolution code (or core data's automatic conflict resolution) will fix any problems that arise.

Still, I hope Apple fixes the race condition bug. That just shouldn't happen.

So, at the time of this writing at least, iCloud downloads using this API should be treated as unreliable.

(Additional reference)


Encountered this same situation myself recently -- just as above, I saw the download clearly happening, but NSURLUbiquitousItemIsDownloading, and all other keys, always returned false. A colleague mentioned that an iCloud engineer had recommended creating a new NSURL to check these NSURLUbiquitousItem keys since the metadata may not (and apparently will not) update after it's created. I created a new NSURL prior to checking, and it indeed reflected the current state.

Not to discount the race conditions mentioned by @MrGomez, which are significant problems (especially prevalent in iOS 5, and caused us many headaches), but I don't believe explain the issue described above.

EDIT:In order to create the new NSURL I used [NSURL fileURLWithPath:originalURL.path]. While a bit messy, it was the first thing the worked reliably. I just tried [originalURL copy] and ended up with old metadata again, so apparently it too was copied.

For safety, or until its documented further, I plan on assuming that unless a new NSURL is created prior to any getResourceValue:forKey: call, stale metadata will be returned.