Вызов - [NSFileManager setUbiquitous: itemAtURL: destinationURL: error:] никогда не возвращается - PullRequest
4 голосов
/ 12 ноября 2011

У меня есть простое приложение для Mac OS X на базе NSDocument, в котором я пытаюсь реализовать хранилище документов iCloud.Я строю с 10,7 SDK.

Я подготовил свое приложение для хранения документов iCloud и включил необходимые разрешения (AFAICT).Приложение правильно собирает, запускает и создает локальный каталог Documents для контейнера вездесущих (это заняло некоторое время, но, похоже, все работает).Я использую 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 ]

5 голосов
/ 12 ноября 2011

Я не знаю, являются ли они источником ваших проблем, но вот некоторые вещи, которые я вижу:

  • -[NSFileManager URLForUbiquityContainerIdentifier:] может занять некоторое время, поэтому не следует вызывать его в главном потоке. см. Раздел «Поиск контейнера Ubiquity» в этом блоге

  • Выполнение этого в глобальной очереди означает, что вам, вероятно, следует использовать выделенный NSFileManager, а не +defaultManager.

  • Блок, переданный в часть byAccessor скоординированной записи, не гарантированно вызывается в каком-либо конкретном потоке, поэтому вам не следует манипулировать NSWindows или представлять модальные диалоги или что-либо из этого блока (если вы не отправили его обратно в основную очередь).

  • Я думаю, что почти все методы iCloud в NSFileManager будут блокироваться, пока все не будет завершено. Возможно, вы видите, что метод блокируется и никогда не возвращается, потому что все настроено неправильно. Я бы дважды и трижды проверил ваши настройки, возможно, попытался бы упростить случай воспроизведения. Если это все еще не работает, попробуйте сообщить об ошибке или связаться с DTS.

3 голосов
/ 17 ноября 2011

Просто поделился этим в Твиттере с вами, но я полагаю, что при использовании NSDocument вам не нужно ничего делать с NSFileCoordinator - просто сделайте документ вездесущим и сохраните.

1 голос
/ 23 января 2012

Хорошо, так что я наконец-то смог решить проблему, используя совет Данка.Я почти уверен, что проблема, с которой я столкнулся, заключается в следующем:

  • Через некоторое время после того, как видео WWDC, которое я использовал в качестве руководства, было создано, Apple завершила работу с API повсеместности и устранила необходимость использовать1004 * объект при сохранении из подкласса NSDocument.

Таким образом, ключом было удалить как создание NSFileCoordinator, так и вызов -[NSFileCoordinator coordinateWritingItemAtURL:options:writingItemAtURL:options:error:byAccessor:]

Iтакже перенес эту работу в фоновую ветку, хотя я совершенно уверен, что для решения проблемы не было абсолютно необходимости (хотя это, безусловно, было хорошей идеей).

Теперь я отправлю свой законченный код в сеть 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:&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);
}
1 голос
/ 19 ноября 2011

Если это первое место в вашем коде, к которому вы обращаетесь к iCloud, посмотрите в Console.app сообщение, подобное этому:

taskgated: kill yourAppID [pid 13532], потому чтоиспользование права com.apple.developer.ubiquity-container-идентификаторов не допускается

Каждый раз, когда вы видите это сообщение, удаляйте контейнер приложений ~/Library/Containers/<yourAppID> В Console.app могут также присутствовать другие полезные сообщения, которые помогутВы решаете эту проблему.Я обнаружил, что удаление контейнера приложения является новым Чистым проектом при работе с iCloud.

1 голос
/ 17 ноября 2011

Хм,

Вы пытались не использовать идентификатор кода повсеместности в коде (извините - вырвано из проекта, поэтому некоторые из них псевдокодированы):

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, но сам все катлю).

...