4

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

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

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

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

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

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

这是我的NSDocument子类中的相关代码。它几乎与 Apple 的 WWDC 演示代码相同。由于这是一个动作,因此在主线程上调用它(如 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];
        }
    }];
}
4

5 回答 5

5

我不知道这些是否是您的问题的根源,但这里有一些事情我看到:

  • -[NSFileManager URLForUbiquityContainerIdentifier:]可能需要一段时间,所以你不应该在主线程上调用它。 请参阅此博客文章的“定位 Ubiquity 容器”部分

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

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

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

于 2011-11-12T04:14:10.333 回答
3

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

于 2011-11-16T22:28:28.353 回答
1

唔,

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

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 而是自己滚动它)。

于 2011-11-16T22:16:15.010 回答
1

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

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

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

于 2011-11-18T21:35:01.873 回答
1

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

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

所以关键是删除创建NSFileCoordinator和调用-[NSFileCoordinator coordinateWritingItemAtURL:options:writingItemAtURL: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);
}
于 2012-01-23T02:11:08.083 回答