【问题标题】:Method for downloading iCloud files? Very confusing?下载iCloud文件的方法?很混乱?
【发布时间】:2012-04-03 08:26:51
【问题描述】:

我的应用程序中有基本的 iCloud 支持(同步更改、无处不在等),但到目前为止,一个关键的遗漏是缺乏对云中存在(或有更改)文件的“下载”支持,但与当前磁盘上的内容不同步。

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

下载方式:

- (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. 即使在两次检查之间文件大小发生变化,我也会永远陷入循环中。

我做错了什么?

【问题讨论】:

    标签: objective-c ios macos icloud


    【解决方案1】:

    我也遇到了同样的问题,我已经花了很多天试图找到解决此错误的方法...

    不幸的是,这里的解决方案都不适合我,我发现我要打开的文档在 2 次尝试打开后始终是最新的。

    所以我的解决方案是用这种方式打开文档两次:

    • 先做个盲口;
    • 盲关;
    • 等待下载状态;
    • 然后打开文档。

    我知道这是一种非常肮脏的工作方式,但对我来说效果很好:-)

    【讨论】:

      【解决方案2】:

      几年后,我仍然遇到定期(尽管在其他答案中的一些变通办法之后更加罕见)下载文件的问题。因此,我联系了 Apple 的开发人员,要求进行技术审查/讨论,这就是我的发现。

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

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

      【讨论】:

      • 当范围定义为 url 时,它对我不起作用。
      • 目前这对我不起作用(8.1.2),将范围设置为 URL 或 NSMetadataQueryUbiquitousDocumentsScope,并将已弃用的 NSMetadataUbiquitousItemIsDownloadedKey 更改为 NSMetadataUbiquitousItemDownloadingStatusKey liveUpdate: 回调永远不会发生。
      • 我可能还注意到,有效的 UbiquityContainer URL 上的resourceValuesForKeys 总是为我返回一个空数组。
      • @voidref 我建议单独提问。如果您认为这是一个错误,您可以联系 DTS @Apple,他们将能够解决它。
      • 你忘记删除观察者了。
      【解决方案3】:

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

      不要忽视@MrGomez 提到的竞争条件,这是重大问题(尤其是在 iOS 5 中普遍存在,让我们很头疼),但我不相信解释上述问题。

      编辑: 为了创建新的NSURL,我使用了[NSURL fileURLWithPath:originalURL.path]。虽然有点混乱,但这是可靠工作的第一件事。我刚刚尝试了[originalURL copy],最后又得到了旧的元数据,所以显然它也被复制了。

      为了安全,或者在进一步记录之前,我计划假设除非在任何 getResourceValue:forKey: 调用之前创建新的 NSURL,否则将返回陈旧的元数据。

      【讨论】:

      • 那么当提供NSURL 对象来检查时,如何创建新的NSURL 来检查更新状态?您是在旧的上调用copy,还是使用其他构造函数,并从原始构造函数中获得值?我假设您在每次下载“通过”开始时重新创建这个新的NSURL
      【解决方案4】:

      这是一个已知问题。已被herehere 转载。

      添加延迟似乎有助于缓解未知的竞争条件,但尚无已知的解决方法。从 3 月 21 日开始:

      不管怎样,我想知道你是否曾经解决过你的主要问题 文章?也就是说,随着时间的推移,同步似乎会降级并且导入 通知停止到达或不完整。你已经确定 添加延迟响应进口通知有一些 价值,但最终证明是不可靠的。

      来自链接文章的OP

      我遇到的问题似乎是由竞争条件引起的。有时候我 会收到我的持久存储已更新的通知 来自 iCloud——但更新的信息尚不可用。 这似乎发生了大约 1/4 的时间,没有延迟,并且 大约有 1/12 的时间有延迟。

      稳定性并没有降低...系统总是会捕获 下次我启动应用程序时的更新,以及自动冲突 解决了这个问题。然后它将继续发挥作用 一般。但是,最终它会放弃另一个更新。

      ...

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

      不过,我希望 Apple 修复竞态条件错误。那不应该 发生。

      因此,至少在撰写本文时,使用此 API 的 iCloud 下载应被视为不可靠。

      (Additional reference)

      【讨论】:

      • 感谢您的链接 - 非常不幸的是,Apple 满足于让事情处于如此糟糕的状态。
      • @MrGomez ...显然我在这里遇到了同样的情况。我应该把延迟放在哪里?那部分对我来说不是很清楚:(
      • @nacho4d 我建议您查看上述关于该问题的文章链接的code package(ZIP 文件)。简而言之,您需要在我理解的每个单独文件的传输之间添加延迟。如果您需要其他帮助,我建议您发布一个后续问题,并附上您的特定代码。祝你好运! :)
      • 这个问题有新的解决方案吗?我在尝试从 iCloud 加载文件时遇到了这个问题。我在if([file getResourceValue... 语句之前添加了[NSThread sleepForTimeInterval:(rand() % 10)];。它有一点帮助,但有没有更好的地方来解决延迟?
      • @Casper 不幸的是,我没有对这个问题进行持续监控。如果此处和其他问题中的建议不能解决您的问题,我的建议是开始一个新问题,并作为您尝试过的内容链接回该线程。我希望我现在有更多的时间来研究这个......但我相信其他人会这样做,他们会很乐意回答你的问题。 :)
      猜你喜欢
      • 2016-07-19
      • 2012-01-29
      • 2019-04-07
      • 2021-02-25
      • 2023-04-03
      • 1970-01-01
      • 2012-12-17
      • 1970-01-01
      • 2018-10-02
      相关资源
      最近更新 更多