Objective c 调用-[NSFileManager setObusible:ItemAttribute:destinationURL:error:]永远不会返回

Objective c 调用-[NSFileManager setObusible:ItemAttribute:destinationURL:error:]永远不会返回,objective-c,macos,cocoa,icloud,icloud-api,Objective C,Macos,Cocoa,Icloud,Icloud Api,我有一个基于Mac OS X的简单的NSDocument,我正试图在其中实现iCloud文档存储。我正在使用10.7SDK进行构建 我已经为iCloud文档存储配置了我的应用程序,并包含了必要的权限(AFAICT)。应用程序正确地构建、运行和创建本地ubiquity容器文档目录(这花了一段时间,但似乎一切都正常)。我正在使用苹果推荐的NSFileCoordinatorAPI。我相当肯定我使用的是苹果推荐的正确的泛素识别器(它在tho下面进行了编辑) 在本WWDC 2011视频中,我密切关注苹果的

我有一个基于Mac OS X的简单的
NSDocument
,我正试图在其中实现iCloud文档存储。我正在使用10.7SDK进行构建

我已经为iCloud文档存储配置了我的应用程序,并包含了必要的权限(AFAICT)。应用程序正确地构建、运行和创建本地ubiquity容器文档目录(这花了一段时间,但似乎一切都正常)。我正在使用苹果推荐的
NSFileCoordinator
API。我相当肯定我使用的是苹果推荐的正确的
泛素识别器(它在tho下面进行了编辑)

在本WWDC 2011视频中,我密切关注苹果的iCloud文档存储演示说明:

会话107在Lion中自动保存和保存版本

我的代码看起来与演示中的代码几乎相同

但是,当我调用将当前文档移动到云中的操作时,我在调用
-[NSFileManager setounious:itemature:destinationURL:error:
方法时遇到了活动性问题。它永远不会回来

下面是我的
NSDocument
子类中的相关代码。它与苹果的WWDC演示代码几乎相同。因为这是一个动作,所以在主线程上调用它(正如苹果的演示代码所示)。当调用
-setUbiouse:ItemAttribute:destinationURL:error:
方法时,死锁会在接近结束时发生。我已经尝试移动到背景线程,但它仍然不会返回

在等待一个从未到达的信号时,信号量似乎正在阻塞

在调试器中运行此代码时,我的源URL和目标URL看起来是正确的,因此我相当确定它们计算正确,并且我已确认磁盘上存在目录

我是否做了任何明显错误的事情,这会导致
-setup
永远不会回来

- (IBAction)moveToOrFromCloud:(id)sender {
    NSURL *fileURL = [self fileURL];
    if (!fileURL) return;

    NSString *bundleID = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIdentifier"];
    NSString *appID = [NSString stringWithFormat:@"XXXXXXX.%@.macosx", bundleID];

    BOOL makeUbiquitous = 1 == [sender tag];

    NSURL *destURL = nil;
    NSFileManager *mgr = [NSFileManager defaultManager];
    if (makeUbiquitous) {
        // get path to local ubiquity container Documents dir
        NSURL *dirURL = [[mgr URLForUbiquityContainerIdentifier:appID] URLByAppendingPathComponent:@"Documents"];
        if (!dirURL) {
            NSLog(@"cannot find URLForUbiquityContainerIdentifier %@", appID);
            return;
        }

        // create it if necessary
        [mgr createDirectoryAtURL:dirURL withIntermediateDirectories:NO attributes:nil error:nil];

        // ensure it exists
        BOOL exists, isDir;
        exists = [mgr fileExistsAtPath:[dirURL relativePath] isDirectory:&isDir];
        if (!(exists && isDir)) {
            NSLog(@"can't create local icloud dir");
            return;
        }

        // append this doc's filename
        destURL = [dirURL URLByAppendingPathComponent:[fileURL lastPathComponent]];
    } else {
        // get path to local Documents folder
        NSArray *dirs = [mgr URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
        if (![dirs count]) return;

        // append this doc's filename
        destURL = [[dirs objectAtIndex:0] URLByAppendingPathComponent:[fileURL lastPathComponent]];
    }

    NSFileCoordinator *fc = [[[NSFileCoordinator alloc] initWithFilePresenter:self] autorelease];
    [fc coordinateWritingItemAtURL:fileURL options:NSFileCoordinatorWritingForMoving writingItemAtURL:destURL options:NSFileCoordinatorWritingForReplacing error:nil byAccessor:^(NSURL *fileURL, NSURL *destURL) {

        NSError *err = nil;
        if ([mgr setUbiquitous:makeUbiquitous itemAtURL:fileURL destinationURL:destURL error:&err]) {
            [self setFileURL:destURL];
            [self setFileModificationDate:nil];
            [fc itemAtURL:fileURL didMoveToURL:destURL];
        } else {
            NSWindow *win = ... // get my window
            [self presentError:err modalForWindow:win delegate:nil didPresentSelector:nil contextInfo:NULL];
        }
    }];
}

我不知道这些是否是你问题的根源,但以下是我看到的一些事情:

  • -[NSFileManager URLForUbiquityContainerIdentifier:
    可能需要一段时间,因此不应在主线程上调用它

  • 在全局队列上执行此操作意味着您可能应该使用已分配的
    NSFileManager
    ,而不是
    +defaultManager

  • 传递给协调写入的
    byAccessor
    部分的块不能保证在任何特定线程上调用,因此您不应该操作
    NSWindows
    ,或显示该块中的模式对话框或任何内容(除非您已将其调度回主队列)

  • 我认为在事情完成之前,
    NSFileManager
    上几乎所有的iCloud方法都会被阻塞。您看到的可能是方法阻塞并且永远不会返回,因为没有正确配置。我会反复检查你的设置,也许可以简化复制的过程。如果仍然不起作用,请尝试提交错误或联系DTS

    • 您是否尝试过在代码中不使用ubiquity容器标识符(抱歉-从项目中删除了,所以我对其中一些内容进行了伪编码):

      然后在您的权利文件中:

      <key>com.apple.developer.ubiquity-container-identifiers</key>
      <array>
          <string>[devID].com.yourcompany.appname</string>
      </array>
      
      com.apple.developer.ubiquity-container-identifiers
      [devID].com.yourcompany.appname
      

      除此之外,您的代码看起来与我的代码几乎相同(这很有效——只是我没有使用NSDocument,而是自己滚动它)。

      刚刚在Twitter上与您分享了这一点,但我相信,在使用NSDocument时,您不需要做任何NSFileCoordinator的工作,只需使文档无处不在并保存即可。

      如果这是您在代码中访问iCloud的第一个位置,请在Console.app中查找以下消息:

      taskgated:杀死了yourAppID[pid 13532],因为它不允许使用com.apple.developer.ubiquity-container-identifiers权限

      每当您看到此消息时,请删除您的应用程序容器
      ~/Library/Containers/
      Console.app中可能还有其他有用的消息可以帮助您解决此问题。
      我发现在使用iCloud时,删除应用程序容器是一个全新的清洁项目。

      好的,所以我终于能够使用Dunk的建议解决这个问题。我很确定我遇到的问题如下:

      • 在我用作指南的WWDC视频制作完成后的某个时候,苹果完成了ubiquity API,不再需要使用
        NSFileCoordinator
        对象,同时从
        NSDocument
        子类中保存
      因此,关键是要删除创建
      NSFileCoordinator
      和调用
      -[NSFileCoordinator CoordinateWritingItemAttribute:options:WritingItemAttribute:options:error:byAccessor:

      我还将这项工作转移到了一个背景线程上,尽管我相当确定这不是解决问题所必需的(尽管这肯定是一个好主意)

      我现在将把我完成的代码提交给谷歌的网络爬虫,希望能帮助未来无畏的Xcoder

      以下是我的完整解决方案:

      - (IBAction)moveToOrFromCloud:(id)sender {
          NSURL *fileURL = [self fileURL];
          if (!fileURL) {
              NSBeep();
              return;
          }
      
          BOOL makeUbiquitous = 1 == [sender tag];
      
          if (makeUbiquitous) {
              [self displayMoveToCloudDialog];
          } else {
              [self displayMoveFromCloudDialog];
          }
      
          dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
              [self doMoveToOrFromCloud:makeUbiquitous];
          });
      }
      
      
      - (void)doMoveToOrFromCloud:(BOOL)makeUbiquitous {
          NSURL *fileURL = [self fileURL];
          if (!fileURL) return;
      
          NSURL *destURL = nil;
          NSFileManager *mgr = [[[NSFileManager alloc] init] autorelease];
          if (makeUbiquitous) {
              NSURL *dirURL = [[MyDocumentController instance] ubiquitousDocumentsDirURL];
              if (!dirURL) return;
      
              destURL = [dirURL URLByAppendingPathComponent:[fileURL lastPathComponent]];
          } else {
              // move to local Documentss folder
              NSArray *dirs = [mgr URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
              if (![dirs count]) return;
      
              destURL = [[dirs firstObject] URLByAppendingPathComponent:[fileURL lastPathComponent]];
          }
      
          NSError *err = nil;
          void (^completion)(void) = nil;
      
          if ([mgr setUbiquitous:makeUbiquitous itemAtURL:fileURL destinationURL:destURL error:&err]) {
              [self setFileURL:destURL];
              [self setFileModificationDate:nil];
      
              completion = ^{
                  [self hideMoveToFromCloudDialog];
              };
      
          } else {
              completion = ^{
                  [self hideMoveToFromCloudDialog];
                  NSWindow *win = [[self canvasWindowController] window];
                  [self presentError:err modalForWindow:win delegate:nil didPresentSelector:nil contextInfo:NULL];
              };
          }
      
          dispatch_async(dispatch_get_main_queue(), completion);
      }
      

      谢谢你,戴夫!我将实施的好主意。不幸的是,我不认为这是我陷入僵局的任何原因,但我应该以任何方式解决这三个问题。再次感谢。嗯,在为每个问题制定了一个快速修复方案之后,我仍然看到死锁(尽管它现在在后台线程而不是主线程上)。谜团依然存在,但再次感谢您,这些建议肯定会改进代码。这就成功了。我已经在下面我自己的“答案”中完整地记录了修复。谢谢Dunk!事实上,苹果自己的文档说你应该使用NSFileCoordinator,但从其余的iCloudAPI来看,这只是另一个错误。(最有可能的是,Setubuquitus做了NSI文件协调器的工作
      - (IBAction)moveToOrFromCloud:(id)sender {
          NSURL *fileURL = [self fileURL];
          if (!fileURL) {
              NSBeep();
              return;
          }
      
          BOOL makeUbiquitous = 1 == [sender tag];
      
          if (makeUbiquitous) {
              [self displayMoveToCloudDialog];
          } else {
              [self displayMoveFromCloudDialog];
          }
      
          dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
              [self doMoveToOrFromCloud:makeUbiquitous];
          });
      }
      
      
      - (void)doMoveToOrFromCloud:(BOOL)makeUbiquitous {
          NSURL *fileURL = [self fileURL];
          if (!fileURL) return;
      
          NSURL *destURL = nil;
          NSFileManager *mgr = [[[NSFileManager alloc] init] autorelease];
          if (makeUbiquitous) {
              NSURL *dirURL = [[MyDocumentController instance] ubiquitousDocumentsDirURL];
              if (!dirURL) return;
      
              destURL = [dirURL URLByAppendingPathComponent:[fileURL lastPathComponent]];
          } else {
              // move to local Documentss folder
              NSArray *dirs = [mgr URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
              if (![dirs count]) return;
      
              destURL = [[dirs firstObject] URLByAppendingPathComponent:[fileURL lastPathComponent]];
          }
      
          NSError *err = nil;
          void (^completion)(void) = nil;
      
          if ([mgr setUbiquitous:makeUbiquitous itemAtURL:fileURL destinationURL:destURL error:&err]) {
              [self setFileURL:destURL];
              [self setFileModificationDate:nil];
      
              completion = ^{
                  [self hideMoveToFromCloudDialog];
              };
      
          } else {
              completion = ^{
                  [self hideMoveToFromCloudDialog];
                  NSWindow *win = [[self canvasWindowController] window];
                  [self presentError:err modalForWindow:win delegate:nil didPresentSelector:nil contextInfo:NULL];
              };
          }
      
          dispatch_async(dispatch_get_main_queue(), completion);
      }