【问题标题】:Calling -[NSFileManager setUbiquitous:itemAtURL:destinationURL:error:] never returns调用 -[NSFileManager setUbiquitous:itemAtURL:destinationURL:error:] 永远不会返回
【发布时间】:2026-01-22 00:55:01
【问题描述】:

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

我已为 iCloud 文档存储配置了我的应用程序,并包含了必要的权利 (AFAICT)。该应用程序正确构建、运行和创建本地通用容器 Documents 目录(这需要一段时间,但一切似乎都在工作)。我正在使用 Apple 推荐的 NSFileCoordinator API。我很确定我使用的是 Apple 推荐的正确的 UbiquityIdentifier(它在下面被删节了)。

我在这个 WWDC 2011 视频中密切关注 Apple 的 iCloud 文档存储演示说明:

第 107 节自动保存和 Lion 中的版本

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

但是,当我调用我的 action 以将当前文档移动到云端时,我在调用 -[NSFileManager setUbiquitous:itemAtURL:destinationURL:error:] 方法时遇到了liveness 问题。它永远不会回来。

这是来自我的NSDocument 子类的相关代码。它几乎与 Apple 的 WWDC 演示代码相同。由于这是一个 action,因此在主线程上调用它(如 Apple 的演示代码所示)。当调用-setUbiquitous:itemAtURL:destinationURL:error: 方法时,死锁会在接近尾声时发生。我已经尝试移动到后台线程,但它仍然永远不会返回。

似乎信号量在等待永远不会到达的信号时阻塞。

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

我是否在做任何明显错误的事情会导致-setUbiquitous 永远不会返回?

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

【问题讨论】:

    标签: objective-c macos cocoa icloud icloud-api


    【解决方案1】:

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

    • 在我用作指导的 WWDC 视频制作完成后的某个时间,Apple 完成了无处不在的 API,并消除了使用 NSFileCoordinator 对象的需要,同时从 NSDocument 子类中保存。

    所以关键是删除NSFileCoordinator的创建和对-[NSFileCoordinator coordinateWritingItemAtURL:options:writingItemAtURL:options:error:byAccessor:]的调用

    我也将这项工作转移到了后台线程,尽管我相当肯定这并不是解决问题所必需的(尽管这肯定是个好主意)。

    我现在将把我完成的代码提交给 Google 的网络爬虫,希望能帮助未来勇敢的 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);
    }
    

    【讨论】:

      【解决方案2】:

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

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

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

      【讨论】:

        【解决方案3】:

        刚刚在 Twitter 上与你分享了这个,但我相信在使用 NSDocument 时,你不需要做任何 NSFileCoordinator 的事情 - 只需让文档无处不在并保存即可。

        【讨论】:

        • 这成功了。我已经在下面我自己的“答案”中完整地记录了修复。谢谢邓克!
        • 实际上,Apple 自己的文档说您应该使用 NSFileCoordinator,但从 iCloud API 的其余部分来看,这只是另一个错误。 (很可能,setUbuquitous 会自己处理 NSFileCoordinator 的内容,这可能会导致冲突/死锁。)
        【解决方案4】:

        嗯,

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

        NSFileManager *fm = [NSFileManager defaultManager];
        NSURL *iCloudDocumentsURL = [[fm URLForUbiquityContainerIdentifier:nil] URLByAppendingPathComponent:@"Documents"];
        NSURL *iCloudFileURL = [iCloudDocumentsURL URLByAppendingPathComponent:[doc.fileURL lastPathComponent]];
        ok = [fm setUbiquitous:YES itemAtURL:doc.fileURL destinationURL:iCloudRecipeURL error:&err];
        NSLog(@"doc moved to iCloud, result: %d (%@)",ok,doc.fileURL.fileURL);
        

        然后在您的权利文件中:

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

        除此之外,你的代码看起来和我的几乎一模一样(它可以工作——除了我没有使用 NSDocument 而是自己滚动它)。

        【讨论】:

          【解决方案5】:

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

          • -[NSFileManager URLForUbiquityContainerIdentifier:] 可能需要一段时间,因此您不应在主线程上调用它。 see the "Locating the Ubiquity Container" section of this blog post

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

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

          • 我认为NSFileManager 上的几乎所有 iCloud 方法都会阻塞,直到事情完成。您看到的可能是方法阻塞并且永远不会返回,因为配置不正确。我会仔细检查您的设置,也许会尝试简化复制案例。如果仍然无法正常工作,请尝试提交错误或联系 DTS。

          【讨论】:

          • 谢谢戴夫!我将实施的好主意。不幸的是,我认为这不是我遇到僵局的原因,但我应该以任何一种方式解决所有这三个问题。再次感谢。
          • 嗯,在对每个问题进行快速修复后,我仍然看到死锁(尽管它现在位于后台线程而不是主线程上)。谜团仍然存在,但再次感谢,这些建议肯定会改进代码。
          最近更新 更多