我有一个简单的基于NSDocument
的Mac OS X应用程序,我正在尝试实现iCloud Document存储。我正在使用10.7 SDK构建。
我已经为iCloud文档存储配置了我的应用程序,并包含了必要的权利(AFAICT)。该应用程序正确构建,运行和创建本地ubiquity容器文档目录(这需要一段时间,但所有似乎都工作)。我正在使用Apple推荐的NSFileCoordinator
API。我很确定我使用Apple推荐的正确UbiquityIdentifier
(它在以下编辑)。
我在此WWDC 2011视频中密切关注Apple的iCloud Document存储演示说明:
会话107狮子会自动保存和版本
我的代码与该演示中的代码几乎完全相同。
但是,当我调用操作将当前文档移动到云时,我会在调用-[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];
}
}];
}
答案 0 :(得分:5)
我不知道这些是否是你问题的根源,但这里有一些我看到的东西:
-[NSFileManager URLForUbiquityContainerIdentifier:]
可能需要一段时间,因此您不应在主线程上调用它。 see the "Locating the Ubiquity Container" section of this blog post
在全局队列上执行此操作意味着您应该使用已分配的NSFileManager
而不是+defaultManager
。
不保证在任何特定线程上调用传递给协调写入的byAccessor
部分的块,因此您不应该操纵NSWindows
或呈现模态对话框或任何内容从该区块内(除非您已将其发送回主队列)。
我认为NSFileManager
上的所有iCloud方法都会阻塞,直到完成。您所看到的可能是方法阻塞并且永远不会返回,因为事情配置不正确。我会对您的设置进行双重和三重检查,也许会尝试简化再现情况。如果仍然无效,请尝试提交错误或联系DTS。
答案 1 :(得分:3)
刚刚在Twitter上与你分享了这个,但我相信在使用NSDocument时你不需要做任何NSFileCoordinator的东西 - 只需使文档无处不在并保存。
答案 2 :(得分: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,而是自己滚动它)。
答案 3 :(得分:1)
如果这是您访问iCloud的代码中的第一个位置,请在Console.app中查找如下消息:
taskgated:kill yourAppID [pid 13532]因为不允许使用com.apple.developer.ubiquity-container-identifiers权利
只要您看到此消息,就会删除您的应用容器~/Library/Containers/<yourAppID>
Console.app中可能还有其他有用的消息可以帮助您解决此问题。
我发现在使用iCloud时删除app容器是新的Clean Project。
答案 4 :(得分:1)
好的,所以我终于能够使用Dunk的建议解决问题了。我很确定我遇到的问题如下:
NSFileCoordinator
子类中保存时无需使用NSDocument
对象。所以关键是要删除NSFileCoordinator
的创建和-[NSFileCoordinator coordinateWritingItemAtURL:options:writingItemAtURL:options:error:byAccessor:]
的调用
我也把这项工作转移到后台线程,虽然我很确定并不是绝对需要解决这个问题(尽管这当然是一个好主意)。
我现在将我已完成的代码提交给Google的网络抓取工具,以期协助未来的强悍Xcoders。
这是我的完整解决方案:
- (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);
}