Objective c 调用-[NSFileManager setObusible:ItemAttribute:destinationURL:error:]永远不会返回
我有一个基于Mac OS X的简单的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视频中,我密切关注苹果的
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);
}