Создание подкласса NSDocument

NSDocument подкласс обеспечивает хранение для модели и возможности загрузить и сохранить данные документа. Это также имеет любые выходы и действия, требуемые для пользовательского интерфейса. NSDocument объект автоматически создает NSWindowController объект управлять тем файлом пера, но NSDocument объект служит объектом прокси Владельца Файла для файла пера.

Когда Вы разделяете на подклассы NSDocument, необходимо переопределить определенные ключевые методы и реализовать других, чтобы сделать, по крайней мере, следующие вещи:

В частности необходимо переопределить чтение того и один метод записи. В самом простом случае можно переопределить основанное на данных чтение и методы записи, readFromData:ofType:error: и dataOfType:error:.

Чтение данных документа

Открытие существующих документов, хранивших в файлах, является одной из наиболее распространенных операций, которые выполняют основанные на документе приложения. Ответственность Вашего переопределения состоит в том, чтобы загрузить данные файла в модель данных Вашего приложения.

Если это работает на Ваше приложение, необходимо переопределить основанный на данных метод чтения, readFromData:ofType:error:. Переопределение того метода делает Вашу работу проще, потому что это использует читающую документ инфраструктуру по умолчанию, предоставленную NSDocument, который может обработать многократные случаи от Вашего имени.

Как переопределить основанный на данных метод чтения

Можно переопределить readFromData:ofType:error: метод для преобразования NSData объект, содержащий данные документа во внутренние структуры данных и дисплей документа, что данные в окне документа. Вызовы архитектуры документа readFromData:ofType:error:, передача в NSData объект, во время его процесса инициализации документа.

Перечисление 4-1 показывает реализацию в качестве примера readFromData:ofType:error: читающий документ метод. Этот пример предполагает, что приложение имеет NSTextView объект, сконфигурированный с NSTextStorage возразите для содержания текстовых данных представления. NSDocument объект имеет a setMString: метод доступа для документа NSAttributedString модель данных, объявленную как свойство, называют mString.

Перечисление 4-1  Основанная на данных читающая документ реализация метода

- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName
                                     error:(NSError **)outError {
    BOOL readSuccess = NO;
    NSAttributedString *fileContents = [[NSAttributedString alloc]
            initWithData:data options:NULL documentAttributes:NULL
            error:outError];
    if (!fileContents && outError) {
        *outError = [NSError errorWithDomain:NSCocoaErrorDomain
                                code:NSFileReadUnknownError userInfo:nil];
    }
    if (fileContents) {
        readSuccess = YES;
        [self setMString:fileContents];
    }
    return readSuccess;
}

Если необходимо иметь дело с расположением файла, переопределите чтение URL и методы записи вместо этого. Если Ваше приложение должно управлять файлами документов, которые являются пакетами файла, переопределяют чтение обертки файла и методы записи вместо этого. Для получения информации о переопределении ОСНОВАННЫХ НА URL и основанных на файле-оберткой методов чтения посмотрите Переопределение URL и Методов Чтения Пакета Файла.

Поток сообщений во время считывания данных документа показан на рисунке 6-5.

Просто поддерживать параллельное открытие документа

Метод класса NSDocument, canConcurrentlyReadDocumentsOfType:, включает Ваш NSDocument разделите на подклассы для загрузки документов одновременно, с помощью фоновых потоков. Это переопределение позволяет параллельное чтение многократных документов и также позволяет приложению быть быстро реагирующим при чтении большого документа. Можно переопределить canConcurrentlyReadDocumentsOfType: возвратиться YES включить эту возможность. Когда Вы делаете, initWithContentsOfURL:ofType:error: выполняется на фоновом потоке когда вводные файлы через Открытое диалоговое окно или от Средства поиска.

Реализация по умолчанию этого метода возвраты NO. Переопределение подкласса должно возвратиться YES только для типов документов, чтение которых кода может быть безопасно выполнено одновременно на неосновных потоках. Если тип документа полагается на информацию об общем состоянии, необходимо возвратиться NO для того типа.

Не полагайтесь на методов get свойства документа в переопределениях чтения методов

Не вызывать fileURL, fileType, или fileModificationDate из Ваших переопределений. Во время чтения, обычно происходящего во время объектной инициализации, нет никакой гарантии этого NSDocument свойства как расположение или тип файла были установлены все же. Ваш переопределенный метод должен быть в состоянии определить все, что он должен сделать чтение от передаваемых параметров. Во время записи Ваш документ можно попросить записать его содержание в различное расположение или использование различного типа файла.

Если Ваше переопределение не может определить всю информацию, этому нужно от передаваемых параметров, рассмотрите переопределение другого метода. Например, если Вы видите потребность вызвать fileURL из переопределения readFromData:ofType:error:, необходимо вместо этого переопределить readFromURL:ofType:error: и используйте переданный - в значении URL.

Запись данных документа

В дополнение к реализации читающего документ метода необходимо реализовать метод записи документа сохранить данные документа на диск. В самом простом случае можно переопределить основанный на данных метод записи, dataOfType:error:. Если это работает на Ваше приложение, необходимо переопределить dataOfType:error:. Переопределение того метода делает Вашу работу проще, потому что это использует читающую документ инфраструктуру по умолчанию, предоставленную NSDocument. Ответственность Вашего переопределения dataOfType:error: метод должен создать и возвратить данные документа поддерживаемого типа, упакованного как NSData объект, в подготовке к записи, что данные к файлу.

Перечисление 4-2 показывает реализацию в качестве примера dataOfType:error:. Как с соответствующим методом чтения документа реализации в качестве примера, этот пример предполагает, что приложение имеет NSTextView объект, сконфигурированный с NSTextStorage возразите для содержания данных документа. Объекту документа подключили свойство выхода с NSTextView возразите и названный textView. Объект документа также синтезировал mString и setMString: средства доступа для документа NSAttributedString модель данных, объявленную как свойство, называют mString.

Перечисление 4-2  Основанная на данных реализация метода записи документа

- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError {
    NSData *data;
    [self setMString:[self.textView textStorage]]; // Synchronize data model with the text storage
    NSMutableDictionary *dict = [NSDictionary dictionaryWithObject:NSRTFTextDocumentType
                                                            forKey:NSDocumentTypeDocumentAttribute];
    [self.textView breakUndoCoalescing];
    data = [self.mString dataFromRange:NSMakeRange(0, [self.mString length])
                    documentAttributes:dict error:outError];
    if (!data && outError) {
        *outError = [NSError errorWithDomain:NSCocoaErrorDomain
                                code:NSFileWriteUnknownError userInfo:nil];
    }
    return data;
}

Переопределение отправляет NSTextView объект a breakUndoCoalescing обменивайтесь сообщениями при сохранении текстового содержания представления для сохранения надлежащего отслеживания несохраненных изменений и грязного состояния документа.

Если для Вашего приложения нужен доступ к файлам документов, можно переопределить writeToURL:ofType:error: вместо этого. Если Ваши данные документа хранятся в пакетах файла, можно переопределить fileWrapperOfType:error: вместо этого. Для получения информации о переопределении другого NSDocument методы записи, посмотрите Переопределение URL и Методов записи Пакета Файла.

Фактический поток сообщений во время этой последовательности событий показан подробно на рисунке 6-6.

Инициализация нового документа

init метод NSDocument определяемый инициализатор, и он вызывается другими инициализаторами initWithType:error: и initWithContentsOfURL:ofType:error:. Если Вы выполняете инициализации, которые должны быть сделаны при создании новых документов, но не при открытии существующих документов, переопределения initWithType:error:. Если у Вас есть какие-либо инициализации, применяющиеся только к документам, открытым, переопределение initWithContentsOfURL:ofType:error:. Если у Вас есть общие инициализации, переопределение init. Во всех трех случаях, убедиться вызвать реализацию суперкласса как первое действие.

Если Вы переопределяете init, удостоверьтесь, что никогда не возвращается Ваше переопределение nil. Возврат nil мог вызвать катастрофический отказ (в некоторых версиях AppKit) или представить меньше, чем полезное сообщение об ошибке. Если, например, Вы хотите предотвратить создание или открытие документов при обстоятельствах, уникальных для Вашего приложения, переопределите определенное NSDocumentController метод вместо этого. Т.е. необходимо управлять этим поведением непосредственно в логике уровня приложения (для предотвращения создания документа или открывающийся в определенных случаях) вместо того, чтобы ловить ситуацию после того, как уже началась инициализация документа.

Реализация awakeFromNib инициализировать объекты, разархивированные от файлов пера окна документа (но не сам документ).

Движущиеся Данные Документа к и от iCloud

Технология хранения iCloud позволяет Вам совместно использовать документы и другие данные приложения среди многократных компьютеров, выполняющих Ваше основанное на документе приложение. Если у Вас есть версия iOS Вашего основанного на документе приложения, совместно использующего те же форматы данных документа, документы могут быть совместно использованы среди устройств на iOS также, как показано на рисунке 4-1. Изменения, внесенные в файл или каталог на одном устройстве, сохранены локально и затем продвинуты к iCloud с помощью локального демона. Передача файлов к и от каждого устройства очевидна для Вашего приложения.

Рисунок 4-1  , Совместно использующий данные документа через iCloud

Доступом к iCloud управляют с помощью прав, которые приложение конфигурирует через XCode. Если эти права не присутствуют, Вашему приложению препятствуют получить доступ к файлам и другим данным в iCloud. В частности контейнерные идентификаторы для Вашего приложения должны быть объявлены в com.apple.developer.ubiquity-container-identifiers право. Для получения информации о том, как сконфигурировать права Вашего приложения, посмотрите Разработку для App Store и Руководство по Потоку операций Инструментов для Mac.

Всеми файлами и каталогами, сохраненными в iCloud, должен управлять объект, принимающий NSFilePresenter протокол и все изменения, которые Вы вносите в те файлы и каталоги, должны произойти через NSFileCoordinator объект. Предъявитель файла и координатор файла препятствуют тому, чтобы внешние источники изменили файл одновременно, и поставляют соответствующие уведомления другим предъявителям файла. NSDocument реализует методы NSFilePresenter протокол и дескрипторы все связанное с файлом управление для Вас. Все Ваше приложение должно сделать, читается, и запишите данные документа, когда сказали, чтобы сделать так. Убедитесь, что Вы переопределяете autosavesInPlace возвратиться YES включить координацию файла в Вашем NSDocument объект.

Определение, Включен ли iCloud

Рано в выполнении Вашего приложения, прежде чем Вы попытаетесь использовать любые другие интерфейсы iCloud, необходимо вызвать NSFileManager метод URLForUbiquityContainerIdentifier: определить, включено ли хранение iCloud. Этот метод возвращает допустимый URL, когда iCloud включен (и указанный контейнерный каталог доступен), или nil когда отключен iCloud. URLForUbiquityContainerIdentifier: также возвраты nil при указании контейнерного ID, к которому приложению не позволяют получить доступ, или это не существует. В этом случае, NSFileManager возразите регистрирует сообщение к консоли, чтобы помочь диагностировать ошибку.

Перечисление 4-3 иллюстрирует, как определить, включен ли iCloud для файла документа URL, представив сообщение об ошибке пользователю в противном случае и установив значение целевого URL документа к тому из его контейнера iCloud иначе (в подготовке к перемещению документа iCloud с помощью setUbiquitous:itemAtURL:destinationURL:error: метод).

Перечисление 4-3  , Определяющее, включен ли iCloud

NSURL *src = [self fileURL];
NSURL *dest = NULL;
NSURL *ubiquityContainerURL = [[[NSFileManager defaultManager]
                                 URLForUbiquityContainerIdentifier:nil]
                                 URLByAppendingPathComponent:@"Documents"];
    if (ubiquityContainerURL == nil) {
        NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
              NSLocalizedString(@"iCloud does not appear to be configured.", @""),
                              NSLocalizedFailureReasonErrorKey, nil];
        NSError *error = [NSError errorWithDomain:@"Application" code:404
                                         userInfo:dict];
        [self presentError:error modalForWindow:[self windowForSheet] delegate:nil
                             didPresentSelector:NULL contextInfo:NULL];
        return;
        }
        dest = [ubiquityContainerURL URLByAppendingPathComponent:
                                                          [src lastPathComponent]];

Поскольку сообщение указывает nil для контейнерного параметра идентификатора, URLForUbiquityContainerIdentifier: возвращает первый контейнер, перечисленный в com.apple.developer.ubiquity-container-identifiers право и создает соответствующий каталог, если это еще не существует. Также Вы могли указать контейнерный идентификатор своего приложения — связь команды ID и комплект приложений ID, разделенный периодом для основного контейнерного идентификатора приложения или различного контейнерного каталога. Например, Вы могли объявить строковую константу для контейнерного идентификатора, как в следующем примере, и передать постоянное имя с сообщением.

static NSString *UbiquityContainerIdentifier = @"A1B2C3D4E5.com.domainname.appname";

Метод также добавляет имя файла документа к целевому URL.

Поиск Документов в iCloud

Приложения должны использовать NSMetadataQuery объекты искать элементы в каталогах контейнера iCloud. Метаданные запрашивают результаты возврата только, когда хранение iCloud включено, и соответствующие контейнерные каталоги были созданы. Для получения информации о том, как создать и сконфигурировать поисковые запросы метаданных, видит Руководство по программированию Поиска Метаданных Файла. Для получения информации о том, как выполнить итерации использования каталогов NSFileManager, см. Руководство по программированию Файловой системы.

Перемещение Документа в Хранение iCloud

Для сохранения нового документа к каталогу контейнера iCloud сначала сохраните его локально и затем вызовите NSFileManager метод setUbiquitous:itemAtURL:destinationURL:error: перемещать файл документа в iCloud.

Перечисление 4-4 показывает реализацию в качестве примера метода, перемещающего файл в хранение iCloud. Это принимает источник и целевой URLs от Перечисления 4-3.

Перечисление 4-4  , Перемещающее документ iCloud

dispatch_queue_t globalQueue =
        dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalQueue, ^(void) {
    NSFileManager *fileManager = [[NSFileManager alloc] init];
    NSError *error = nil;
    // Move the file.
    BOOL success = [fileManager setUbiquitous:YES itemAtURL:src
                               destinationURL:dest error:&error];
    dispatch_async(dispatch_get_main_queue(), ^(void) {
        if (! success) {
            [self presentError:error modalForWindow:[self windowForSheet]
                  delegate:nil didPresentSelector:NULL contextInfo:NULL];
        }
    });
});
[self setFileURL:dest];
[self setFileModificationDate:nil];

После того, как файл документа был перемещен в iCloud, как показано в Перечислении 4-4, читение и писание выполняются нормальным NSDocument механизмы, автоматически управляющие координацией доступа к файлу, требуемой iCloud.

Удаление Документа от Хранения iCloud

Для перемещения файла документа из каталога контейнера iCloud выполните ту же процедуру, описанную в Перемещении Документа в Хранение iCloud, кроме переключателя источник URL (теперь файл документа в каталоге контейнера iCloud) и целевой URL (расположение файла документа в локальной файловой системе). Кроме того, первый параметр setUbiquitous:itemAtURL:destinationURL:error: метод должен теперь быть NO.

Для ясности в этом примере называют URL файла в хранении iCloud cloudsrc и локальный URL, в который перемещен файл, называют localdest.

dispatch_queue_t globalQueue =
        dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalQueue, ^(void) {
    NSFileManager *fileManager = [[NSFileManager alloc] init];
    NSError *error = nil;
    // Move the file.
    BOOL success = [fileManager setUbiquitous:NO itemAtURL:cloudsrc
                               destinationURL:localdest error:&error];
 
    dispatch_async(dispatch_get_main_queue(), ^(void) {
        if (! success) {
            [self presentError:error modalForWindow:[self windowForSheet]
                  delegate:nil didPresentSelector:NULL contextInfo:NULL];
        }
    });
});

Для получения дополнительной информации о iCloud, см. Руководство по проектированию iCloud.

NSDocument обрабатывает разрешение конфликтов среди версий документа

NSDocument разрешение конфликтов дескрипторов автоматически, таким образом, Вы не должны реализовывать его сами. В то время как документ открыт, если входит конфликт NSDocument представляет лист, прося, чтобы пользователь разрешил конфликт (или проигнорировать, который отмечает его, как разрешено и принимает автоматического победителя конфликта, обычно тот с новой датой модификации). Щелчок по Resolve вызывает пользовательский интерфейс Версий (см., что Пользователи Могут Просмотреть Версии документа) с только конфликтными видимыми версиями. Пользователь может выбрать определенную версию и нажать Restore, чтобы сделать его победителем конфликта, или просто выбрать Done для принятия автоматического победителя.

Даже после того, как конфликт разрешен, NSDocument всегда сохраняет конфликтные версии, и к ним можно обычно получать доступ через Версии.

Дополнительные переопределения метода

Области, описанные элементами в следующих разделах, требуют переопределений метода в некоторых ситуациях. И, конечно, необходимо реализовать любые методы, которые являются особенными для Вашего NSDocument подкласс. Больше опций для Вашего NSDocument подкласс описан в Альтернативных Конструктивных соображениях.

Создание контроллера окна

NSDocument подклассы должны создать свои контроллеры окна. Они могут сделать это косвенно или непосредственно. Если документ имеет только один файл пера с одним окном в нем, подкласс может переопределить windowNibName возвратить имя файла пера окна. Как следствие архитектура документа создает значение по умолчанию NSWindowController экземпляр для документа, с документом как владелец файла пера. Если документ имеет многократные окна, или если экземпляр пользовательского NSWindowController подкласс используется, NSDocument подкласс должен переопределить makeWindowControllers создать эти объекты.

Если Ваш документ имеет только одно окно, шаблон проекта обеспечивает реализацию по умолчанию NSDocument метод windowNibName:

- (NSString *)windowNibName {
    return @"MyDocument";
}

Если у Вас есть пользовательский подкласс, если Ваш документ имеет больше чем одно окно, или NSWindowController, переопределение makeWindowControllers вместо этого. Удостоверьтесь, что Вы добавляете каждый создаваемый контроллер окна к списку таких объектов, которыми управляет использование документа addWindowController:.

Загрузка файла пера окна

Можно реализовать windowControllerWillLoadNib: и windowControllerDidLoadNib: выполнять любые необходимые задачи имело отношение к окну прежде и после того, как оно загружается из файла пера. Например, Вы, возможно, должны выполнить операции установки на объектах пользовательского интерфейса, таких как установка содержания представления, после того, как были загружены данные модели приложения. В этом случае необходимо помнить что NSDocument методы считывания данных, такой как readFromData:ofType:error:, вызываются, прежде чем объекты пользовательского интерфейса документа, содержавшиеся в его файле пера, загружаются. Конечно, Вы не можете отправить сообщения в объекты пользовательского интерфейса до окончания загрузок файла пера. Так, можно выполнить в таких операциях windowControllerDidLoadNib:.

Вот пример:

- (void)windowControllerDidLoadNib:(NSWindowController *)windowController {
    [super windowControllerDidLoadNib:windowController];
    [textView setAllowsUndo:YES];
    if (fileContents != nil) {
        [textView setString:fileContents];
        fileContents = nil;
    }
}

Печать и макет страницы

Основанное на документе приложение может изменить информацию, которую оно использует, чтобы определить, как распечатаны данные документа. Эта информация инкапсулируется в NSPrintInfo объект. Если Вы хотите, чтобы пользователи были в состоянии распечатать документ, необходимо переопределить printOperationWithSettings:error:, возможно обеспечение измененного NSPrintInfo объект.

Изменение диалогового представления аксессуара сохранения

По умолчанию, когда NSDocument выполняет диалоговое окно Сохранения, и документ имеет многократные перезаписываемые типы документов, это вставляет вспомогательное представление около нижней части диалогового окна. Это представление содержит всплывающее меню перезаписываемых типов. Если Вы не хотите это всплывающее меню, переопределение shouldRunSavePanelWithAccessoryView возвратиться NO. Можно также переопределить prepareSavePanel: сделать дальнейшую настройку диалогового окна Сохранения.

Проверка пунктов меню

NSDocument реализации validateUserInterfaceItem: управлять включенным состоянием пунктов меню Revert Document и Save. Если Вы хотите проверить другие пункты меню, Вы можете переопределить этот метод, но, несомненно, вызовете реализацию суперкласса. Для получения дополнительной информации о проверке пункта меню посмотрите Меню приложения и Раскрывающийся Список, Программируя Темы.