#objective-c #macos #cocoa #icloud #icloud-api
#objective-c #macos #какао #icloud #icloud-api
Вопрос:
У меня есть простое приложение для Mac OS X на NSDocument
основе, в котором я пытаюсь реализовать хранилище документов iCloud. Я создаю с помощью SDK 10.7.
Я подготовил свое приложение для хранения документов iCloud и включил необходимые права (AFAICT). Приложение правильно собирает, запускает и создает локальный каталог документов контейнера ubiquity (это заняло некоторое время, но, похоже, все работает). Я использую NSFileCoordinator
API, как рекомендовала Apple. Я совершенно уверен, что использую правильный, UbiquityIdentifier
как рекомендовано Apple (он отредактирован ниже).
Я внимательно следовал демонстрационным инструкциям Apple по хранению документов iCloud в этом видео WWDC 2011:
Автосохранение сеанса 107 и версий в Lion
Мой код выглядит почти идентично коду из той демонстрации.
Однако, когда я вызываю свое действие для перемещения текущего документа в облако, я испытываю проблемы с живучестью при вызове -[NSFileManager setUbiquitous:itemAtURL:destinationURL:error:]
метода. Он никогда не возвращается.
Вот соответствующий код из моего NSDocument
подкласса. Он почти идентичен демонстрационному коду WWDC от Apple. Поскольку это действие, оно вызывается в главном потоке (как показал демонстрационный код 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:amp;isDir];
if (!(exists amp;amp; 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:amp;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];
}
}];
}
Ответ №1:
Я не знаю, являются ли они источником ваших проблем, но вот некоторые вещи, которые я вижу:
-
-[NSFileManager URLForUbiquityContainerIdentifier:]
может занять некоторое время, поэтому вам не следует вызывать его в основном потоке. смотрите раздел «Поиск контейнера повсеместности» в этом сообщении в блоге -
Выполнение этого в глобальной очереди означает, что вам, вероятно, следует использовать выделенную,
NSFileManager
а неdefaultManager
. -
Блок, переданный в
byAccessor
часть скоординированной записи, не гарантированно вызывается в каком-либо конкретном потоке, поэтому вам не следует манипулироватьNSWindows
или представлять модальные диалоговые окна или что-либо еще из этого блока (если только вы не отправили его обратно в основную очередь). -
Я думаю, что почти все методы iCloud в
NSFileManager
будут заблокированы до завершения работы. Возможно, вы видите, что метод блокируется и никогда не возвращается, потому что что-то настроено неправильно. Я бы дважды и трижды проверил ваши настройки, возможно, попытался бы упростить случай воспроизведения. Если это все еще не работает, попробуйте сообщить об ошибке или связаться с DTS.
Комментарии:
1. Спасибо, Дэйв! Хорошие идеи, которые я буду реализовывать. К сожалению, я не думаю, что что-либо из этого является причиной того, что я сталкиваюсь с тупиковой ситуацией, но я должен решить все три эти проблемы в любом случае. Еще раз спасибо.
2. Хм, после быстрого исправления для каждого из них я все еще вижу взаимоблокировку (хотя теперь она находится в фоновом потоке вместо основного потока). Загадка продолжает существовать, но еще раз спасибо, эти предложения наверняка улучшат код.
Ответ №2:
Только что поделился этим с вами в Twitter, но я считаю, что при использовании NSDocument вам не нужно выполнять какие-либо действия NSFileCoordinator — просто сделайте документ повсеместным и сохраните.
Комментарии:
1. Это сделало свое дело. Я полностью задокументировал исправление в моем собственном «ответе» ниже. Спасибо, Данк!
2. На самом деле, в собственной документации Apple говорится, что вы должны использовать NSFileCoordinator, но, судя по остальным API iCloud, это просто еще одна ошибка. (Скорее всего, setUbuquitous сам заполняет NSFileCoordinator, что, вероятно, приводит к конфликту / взаимоблокировке.)
Ответ №3:
Хм,
вы пробовали не использовать идентификатор контейнера 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:amp;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, а запускаю все это сам).
Ответ №4:
Если это первое место в вашем коде, к которому вы обращаетесь в iCloud, найдите в Console.app сообщение, подобное этому:
taskglated: уничтожен ваш идентификатор [pid 13532], поскольку использование права com.apple.developer.ubiquity-container-identifiers не разрешено
Каждый раз, когда вы видите это сообщение, удалите свой контейнер приложений ~/Library/Containers/<yourAppID>
В Console.app также могут появляться другие полезные сообщения, которые помогут вам решить эту проблему. Я обнаружил, что удаление контейнера приложения — это новый чистый проект при работе с iCloud.
Ответ №5:
Итак, я, наконец, смог решить проблему, воспользовавшись советом Данка. Я почти уверен, что проблема, с которой я столкнулся, заключается в следующем:
- Через некоторое время после создания видеоролика WWDC, который я использовал в качестве руководства, Apple завершила работу с API-интерфейсами ubiquity и устранила необходимость использовать
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:amp;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);
}