6

我有一个将文件存储在 iCloud 中的 iOS 应用程序。当我启动应用程序时,我想确定以前的设备是否已经上传了任何文件。我启动第一台设备并将文件添加到 iCloud(我可以在我的 Mac 上的 Mobile Documents 文件夹中看到它们)。然后我在第二台设备上启动应用程序并尝试使用以下 NSMetadataQuery 来查看是否已上传任何文件,但它返回 0 结果。如果我继续运行该查询,大约 8-10 秒后它会返回结果。

iCloudQuery = [[NSMetadataQuery alloc] init];

iCloudQuery.searchScopes = @[NSMetadataQueryUbiquitousDataScope];

NSString *filePattern = [NSString stringWithFormat:@"*.%@", @"txt"];

iCloudQuery.predicate = [NSPredicate predicateWithFormat:@"%K LIKE %@", NSMetadataItemFSNameKey, filePattern];

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(iCloudQueryDidFinishGathering:) name:NSMetadataQueryDidFinishGatheringNotification object:iCloudQuery];

[iCloudQuery startQuery];

当我收到查询的通知时 resultCount 为 0

- (void)iCloudQueryDidFinishGathering:(NSNotification *)notification
{    
    NSMetadataQuery *query = [notification object];
    [query disableUpdates];
    [query stopQuery];

    NSLog(@"Found %d results from metadata query", query.resultCount);
}

如果文件存在于 iCloud 中,NSMetadataQuery 不应该返回一个 resultCount,即使它没有被下载?除了在 15-30 秒后尝试结束和超时之外,还有什么方法可以测试文件是否存在?

4

1 回答 1

7

查询可能需要一些时间才能从 iCloud 检索元数据。didFinishGathering 最初可能只保存设备已经知道的结果,而不是它没有机会从 iCloud 听到的更改。

与其停止和启动您的 NSMetadataQuery,不如设置一个并通过注册以下内容继续收听它:

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(iCloudQueryDidUpdate:)
                                             name:NSMetadataQueryDidUpdateNotification
                                           object:iCloudQuery];

...并在更新时检索它们。因此,您还需要更改您的 finishGathering 方法,而不是停止查询,并在最后启用更新。

您必须重新考虑您的方法,以考虑到第一组结果不一定知道所有内容的事实。更常见的是,NSMetadataQuery 用于在 iCloud 上保持监视,期望其他设备生成的更改可以随时出现- 而不仅仅是在应用程序启动时。

如果您需要确保您拥有最新的 iCloud 元数据,我发现可靠的唯一方法(在 iOS 5 和 iOS 6 上)是将一个小文件注入iCloud(通常具有不同形式的名称,并使用 UUID 命名,因此保证它是唯一的),然后在 iCloudQueryDidUpdate: 方法中,不考虑查询结果是完整的,直到该文件都被查询返回,并且它的元数据报告它是也上传到 iCloud。一旦你得到这个,你可以相当肯定你已经从 iCloud 收到了最新的元数据。

在 iCloudQueryDidUpdate 中检查上传:使用:

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]];

  // You'll need to check isUploaded against the URL of the file you injected, rather than against any other files your query returns
}

完成后不要忘记删除注入的文件 - 否则每次启动应用程序时这些文件都会挂载。

编辑:

我实施这些检查的方式有一个内在的延迟,一旦我把它拿出来,我发现上面的情况并不完全可靠。

我已删除元数据项目(在当前运行之前使用设置/iCloud/存储和备份/管理存储删除)被报告为已上传和下载并存在于磁盘上,然后才为我注入的文件返回完整的元数据。但是,一旦注入的文件被报告为已上传、下载并在本地磁盘上存在,这些已删除文件之一仍会在元数据中列出为已上传和下载 - 但不存在于磁盘上。

因此,看起来正在发生的事情是 iCloud 守护程序听到有关旧数据的待删除,并在您的应用程序看到的元数据已更新以反映这一点之前实际执行删除。疯了吧?因此,我必须更新我上面的建议,仅当项目被报告为已下载、已上传并且使用该方法存在于本地文件夹中时才考虑查询结果完整。[NSFileManager fileExistsAtPath:]上面的代码已编辑以反映这一点。

在此之后,您所能做的就是在对查询结果采取行动之前延迟 1 秒,以绝对确定所有元数据都有时间接收 - 尽管这是我讨厌必须做的事情。将虚假的时间延迟粘贴到代码中以使其工作对我来说感觉有点太接近黑魔法了。并且表明您并不真正了解发生了什么 - 尽管没有更多挂钩到 iCloud 背后的处理,我们还能做什么?

于 2013-07-06T08:32:59.390 回答