Вызов -[NSFileManager setUbiquitous:itemAtURL:destinationURL:error:] никогда не возвращается
У меня прямолинейный NSDocument
Mac OS X, в котором я пытаюсь реализовать хранилище документов iCloud. Я строю с 10,7 SDK.
Я подготовил свое приложение для хранения документов iCloud и включил необходимые разрешения (AFAICT). Приложение правильно собирает, запускает и создает локальный каталог Document s для контейнера вездесущих (это заняло некоторое время, но, похоже, все работает). Я использую NSFileCoordinator
API, как Apple, рекомендуется. Я вполне уверен, что я использую правильный UbiquityIdentifier
как рекомендовано Apple (отредактировано ниже, чем).
Я внимательно следовал инструкциям Apple по демонстрации хранилища iCloud Document в этом видео WWDC 2011:
Сессия 107 Автосохранение и версии в Lion
Мой код выглядит практически идентично коду из этой демонстрации.
Однако, когда я вызываю свое действие для перемещения текущего документа в облако, у меня возникают проблемы с живостью при вызове -[NSFileManager setUbiquitous:itemAtURL:destinationURL:error:]
метод. Это никогда не возвращается.
Вот соответствующий код из моего NSDocument
подкласс. Он практически идентичен демонстрационному коду Apple WWDC. Поскольку это действие, оно вызывается в основном потоке (как показал демонстрационный код Apple). Тупик возникает в конце, когда -setUbiquitous:itemAtURL:destinationURL:error:
метод называется. Я попытался перейти на фоновый поток, но он по-прежнему не возвращается.
Похоже, что семафор блокируется в ожидании сигнала, который никогда не приходит.
При запуске этого кода в отладчике мои исходные и целевые 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];
}
}];
}
5 ответов
Просто поделился этим в Твиттере с вами, но я полагаю, что при использовании NSDocument вам не нужно делать какие-либо вещи NSFileCoordinator - просто сделайте документ вездесущим и сохраните.
Я не знаю, являются ли они источником ваших проблем, но вот некоторые вещи, которые я вижу:
-[NSFileManager URLForUbiquityContainerIdentifier:]
может занять некоторое время, поэтому вы не должны вызывать его в главном потоке. см. раздел "Поиск контейнера Ubiquity" в этой записи блогаВыполнение этого в глобальной очереди означает, что вы, вероятно, должны использовать выделенный
NSFileManager
а не+defaultManager
,Блок передан в
byAccessor
не гарантируется, что часть скоординированной записи будет вызываться в каком-либо конкретном потоке, поэтому вам не следует манипулироватьNSWindows
или представление модальных диалогов или чего-либо из этого блока (если вы не отправили его обратно в основную очередь).Я думаю, что почти все методы iCloud на
NSFileManager
будет блокировать, пока все не будет завершено. Возможно, вы видите, что метод блокируется и никогда не возвращается, потому что все настроено неправильно. Я бы дважды и трижды проверил ваши настройки, возможно, попытался бы упростить случай воспроизведения. Если это все еще не работает, попробуйте сообщить об ошибке или связаться с DTS.
Если это первое место в вашем коде, к которому вы обращаетесь к iCloud, поищите в Console.app сообщение, подобное этому:
taskgated: убил yourAppID [pid 13532], так как его использование права com.apple.developer.ubiquity-container-identifiers недопустимо
Каждый раз, когда вы видите это сообщение, удалите контейнер приложений ~/Library/Containers/<yourAppID>
Также могут быть другие полезные сообщения в Console.app, которые помогут вам решить эту проблему. Я обнаружил, что удаление контейнера приложения является новым Чистым проектом при работе с iCloud.
Хм,
Вы пытались не использовать идентификатор кода ubiquity в коде (извините - вырвано из проекта, поэтому я кое-что псевдокодировал):
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, но сам все это использую).
Итак, я наконец смог решить проблему, используя совет Данка. Я почти уверен, что у меня возникла следующая проблема:
- Через некоторое время после того, как видео WWDC, которое я использовал в качестве руководства, было сделано, Apple завершила работу с API повсеместности и устранила необходимость использовать
NSFileCoordinator
объект при сохранении изнутриNSDocument
подкласс.
Таким образом, ключ должен был удалить как создание NSFileCoordinator
и призыв к -[NSFileCoordinator coordinateWritingItemAtURL:options:writingItemAtURL:options:error:byAccessor:]
Я также перенес эту работу в фоновый поток, хотя я вполне уверен, что это не было абсолютно необходимо для решения проблемы (хотя это, безусловно, было хорошей идеей).
Теперь я отправлю свой законченный код сканерам Google в надежде помочь будущим бесстрашным X-кодерам.
Вот мое полное решение, которое работает:
- (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);
}