Вызов -[NSFileManager setUbiquitous:itemAtURL:destinationURL:ошибка:] никогда не возвращает

#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);
}