下载iCloud文件的方法? 很混乱?

我在我的应用程序中支持基本的iCloud支持(同步更改,无处不在等),但迄今为止一个至关重要的缺失是缺less对云中存在(或已有更改)文件的“下载”支持,与当前磁盘上的内容同步。

我在我的应用程序中添加了以下方法,基于一些苹果提供的代码,进行了一些调整:

下载方法:

- (BOOL)downloadFileIfNotAvailable:(NSURL*)file { NSNumber* isIniCloud = nil; if ([file getResourceValue:&isIniCloud forKey:NSURLIsUbiquitousItemKey error:nil]) { // If the item is in iCloud, see if it is downloaded. if ([isIniCloud boolValue]) { NSNumber* isDownloaded = nil; if ([file getResourceValue:&isDownloaded forKey:NSURLUbiquitousItemIsDownloadedKey error:nil]) { if ([isDownloaded boolValue]) return YES; // Download the file. NSFileManager* fm = [NSFileManager defaultManager]; NSError *downloadError = nil; [fm startDownloadingUbiquitousItemAtURL:file error:&downloadError]; if (downloadError) { NSLog(@"Error occurred starting download: %@", downloadError); } return NO; } } } // Return YES as long as an explicit download was not started. return YES; } - (void)waitForDownloadThenLoad:(NSURL *)file { NSLog(@"Waiting for file to download..."); id<ApplicationDelegate> appDelegate = [DataLoader applicationDelegate]; while (true) { NSDictionary *fileAttribs = [[NSFileManager defaultManager] attributesOfItemAtPath:[file path] error:nil]; NSNumber *size = [fileAttribs objectForKey:NSFileSize]; [NSThread sleepForTimeInterval:0.1]; NSNumber* isDownloading = nil; if ([file getResourceValue:&isDownloading forKey:NSURLUbiquitousItemIsDownloadingKey error:nil]) { NSLog(@"iCloud download is moving: %d, size is %@", [isDownloading boolValue], size); } NSNumber* isDownloaded = nil; if ([file getResourceValue:&isDownloaded forKey:NSURLUbiquitousItemIsDownloadedKey error:nil]) { NSLog(@"iCloud download has finished: %d", [isDownloaded boolValue]); if ([isDownloaded boolValue]) { [self dispatchLoadToAppDelegate:file]; return; } } NSNumber *downloadPercentage = nil; if ([file getResourceValue:&downloadPercentage forKey:NSURLUbiquitousItemPercentDownloadedKey error:nil]) { double percentage = [downloadPercentage doubleValue]; NSLog(@"Download percentage is %f", percentage); [appDelegate updateLoadingStatusString:[NSString stringWithFormat:@"Downloading from iCloud (%2.2f%%)", percentage]]; } } } 

并开始/检查下载的代码:

  if ([self downloadFileIfNotAvailable:urlToUse]) { // The file is already available. Load. [self dispatchLoadToAppDelegate:[urlToUse autorelease]]; } else { // The file is downloading. Wait for it. [self performSelector:@selector(waitForDownloadThenLoad:) withObject:[urlToUse autorelease] afterDelay:0]; } 

据我可以告诉上面的代码似乎很好,但是当我在设备A上进行大量的更改,保存这些更改,然后打开设备B(提示在设备B上下载),这是我在控制台中看到:

 2012-03-18 12:45:55.858 MyApp[12363:707] Waiting for file to download... 2012-03-18 12:45:58.041 MyApp[12363:707] iCloud download is moving: 0, size is 101575 2012-03-18 12:45:58.041 MyApp[12363:707] iCloud download has finished: 0 2012-03-18 12:45:58.041 MyApp[12363:707] Download percentage is 0.000000 2012-03-18 12:45:58.143 MyApp[12363:707] iCloud download is moving: 0, size is 101575 2012-03-18 12:45:58.143 MyApp[12363:707] iCloud download has finished: 0 2012-03-18 12:45:58.144 MyApp[12363:707] Download percentage is 0.000000 2012-03-18 12:45:58.246 MyApp[12363:707] iCloud download is moving: 0, size is 101575 2012-03-18 12:45:58.246 MyApp[12363:707] iCloud download has finished: 0 2012-03-18 12:45:58.246 MyApp[12363:707] Download percentage is 0.000000 2012-03-18 12:45:58.347 MyApp[12363:707] iCloud download is moving: 0, size is 177127 2012-03-18 12:45:58.347 MyApp[12363:707] iCloud download has finished: 0 2012-03-18 12:45:58.347 MyApp[12363:707] Download percentage is 0.000000 2012-03-18 12:45:58.449 MyApp[12363:707] iCloud download is moving: 0, size is 177127 2012-03-18 12:45:58.449 MyApp[12363:707] iCloud download has finished: 0 2012-03-18 12:45:58.450 MyApp[12363:707] Download percentage is 0.000000 

所以无论什么原因:

  1. 该文件的下载无误地启动
  2. 文件下载状态的文件属性总是返回不下载,还没有完成下载,进度为0%。
  3. 即使文件大小在两次检查之间发生变化 ,我仍然永远留在循环中。

我究竟做错了什么?

这是一个已知的问题。 它在这里和这里被转载。

似乎增加一个延迟有助于缓解一个未知的竞争条件,但尚不存在已知的解决方法。 从3月21日起:

无论如何,我想知道你是否曾经在这篇文章中通过你的主要问题? 也就是说,随着时间的推移,同步似乎降低,import通知停止到达或不完整。 你已经认识到,对import通知做出回应的延迟有一定的价值,但最终certificate是不可靠的。

并从链接的文章的OP :

我所遇到的问题似乎是由于竞赛状况造成的。 有时我会收到通知,说我的持久存储已经从iCloud更新 – 但更新的信息将不可用。 这似乎在没有延迟的情况下发生大约四分之一的时间,并且在延迟时间的大约十二分之一的时间发生。

它不像稳定性下降…系统总是会在下次启动应用程序时捕获更新,并且自动冲突解决scheme解决了问题。 然后它会继续正常工作。 但是,最终,它会放弃另一个更新。

在某种程度上,我认为我们只需要相信iCloud最终会推出这些信息,我们的冲突解决代码(或核心数据的自动冲突解决scheme)将解决所有出现的问题。

不过,我希望苹果能够修复竞争条件的问题。 那应该不会发生。

所以,至less在写这篇文章的时候,使用这个API下载iCloud应该被认为是不可靠的。

( 其他参考 )

几年后,我仍然经历了周期性(尽pipe在其他答案中的一些解决方法之后更罕见)下载文件的问题。 所以,我联系了苹果的开发人员,要求进行技术审查/讨论,这里是我发现的。

定期检查同一个NSURL的下载状态,即使重新创build,也不是检查状态的首选方法。 我不知道为什么它不是 – 它似乎应该工作,但它不。 相反,一旦你开始下载文件,你应该向NSNotificationCenter注册一个观察者,看看下载的进度,并维护对该文件查询的引用。 以下是提供给我的确切代码示例。 我已经在我的应用中实现了它(具有一些特定于应用的调整),并且似乎performance得更合适。

 - (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; } } 

最近自己也遇到过这种情况 – 就像上面那样,我看到下载清楚地发生了,但是NSURLUbiquitousItemIsDownloading和其他所有的键总是返回false 。 一位同事提到,一位iCloud工程师build议创build一个新的NSURL来检查这些NSURLUbiquitousItem键,因为元数据创build后可能没有(显然不会)更新。 在检查之前,我创build了一个新的NSURL,它确实反映了当前状态。

不要忽略@MrGomez提到的竞争条件,这些问题是严重的问题(特别是在iOS 5中普遍存在,并且让我们头痛不已),但我不相信解释上述问题。

编辑:为了创build新的NSURL我使用[NSURL fileURLWithPath:originalURL.path] 。 虽然有点凌乱,但它是第一件可靠的工作。 我只是尝试[originalURL copy]并以旧的元数据再次结束,所以显然它也被复制。

为了安全起见,或者直到进一步logging为止,我打算假定除非在任何getResourceValue:forKey:调用之前创build了新的NSURL ,否则将返回陈旧的元数据。

我面临同样的问题,我花了很多天试图find一个解决这个bug的解决scheme…

不幸的是,这里没有一个解决scheme适用于我,我发现我打开的文档在打开2次后总是处于最新状态。

所以我的解决scheme是这样打开文件两次:

  • 首先要盲目开放;
  • 闭上眼睛;
  • 等待下载的状态;
  • 然后打开文档。

我知道,这是一个非常肮脏的方式来做这项工作,但它对我来说很好:-)