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

У меня прямолинейный NSDocumentMac 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);
}
Другие вопросы по тегам