В реальном мире, где реальный мир ограничен миром компьютерного программного обеспечения, операции отмены являются обычным явлением. Сценарии использования, которые обычно приходят на ум при мысли об отмене операций, - это приложения для повышения производительности, такие как редакторы документов, или приложения для творчества, такие как фоторедакторы. Действительно, в приложении LIVEOP X для iOS компонент рисования также стал нашей первой встречей с NSUndoManager.

Производительность играет ключевую роль в LIVEOP X. В критической ситуации нет места для ошибки, неправильного толкования или «попыток включить или выключить его». Если приложение не отвечает, которого ожидает пользователь, пользователь просто закроет приложение, уберет планшет и окажет первую помощь без всей необходимой информации, которую может предложить приложение. Чтобы предотвратить это и достичь цели создания ценности для пользователя, мы должны установить самые высокие стандарты с точки зрения производительности и производительности приложений. Это начинается с разработки, когда иногда мы получаем помощь из неожиданных источников.

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

Как это выглядит в коде? Очень простой. В то же время, когда линейный сегмент добавляется на карту, мы регистрируем операцию отмены с одним NSUndoManager для того же самого линейного сегмента.

- (void)addHandDrawnAnnotations:(NSArray <id> *)annotations
{
    for (id annotation in annotations) {
        [self.mapController addAnnotation:annotation];
}
    [self.undoManager registerUndoWithTarget:self selector:@selector(removeHandDrawnAnnotations:) object:annotations];
}

API NSUndoManager делают его невероятно простым в использовании. Фактически, его применимость и пригодность в кодовой базе выходит далеко за рамки его типичных сценариев использования, описанных ранее.

Отменить вне четкого пользовательского контекста

На нашем портале конфигурации GEO клиенты могут определять собственные действия и прикреплять их к слоям карты. Затем всякий раз, когда в приложении iOS выбирается аннотация, принадлежащая слою карты, будут выполняться действия, настроенные для этого слоя. Действие может заключаться в выборе дополнительных слоев карты или отображении дополнительных элементов пользовательского интерфейса. Действие - это просто описание, сообщающее приложению, что делать, когда пользователь выбирает аннотацию. Когда действие изменяется в конфигурации сервера, мы просто воссоздаем модель представления, представляющую действие.

Действия выполняются только один раз. Но всякий раз, когда слой карты отменяется программно или пользователем, или когда новая аннотация выбирается из того же слоя карты, мы хотим аннулировать влияние ранее выполненных действий: все действия предполагаются с чистого листа. На первый взгляд, это потребует большого количества сложного кода, проверяющего, какие действия были применены, поиска и сопоставления их с последствиями в приложении и отбрасывания их. Однако невозможно связать последствия (например, дополнительный пользовательский интерфейс, добавленный в результате действия) с моделями представления действия, поскольку модели представления воссоздаются на лету. NSUndoManager представляет собой отличное решение. К тому же, в целом, мы стараемся по возможности избегать нежелательного кода.

В модели представления слоя карты мы определяем NSUndoManager с одной группой. Группа открыта при создании. Затем, когда мы хотим отменить действия слоя, мы закрываем группу, вызываем -undo и открываем новую группу. Мы используем группировку, чтобы избавиться от всех действий сразу. Действия отмены регистрируются одновременно с выполнением действий. В случае действия, которое добавляет кнопку в пользовательский интерфейс, это выглядит следующим образом:

Здесь model - это модель представления слоя карты. Обратите внимание, что NSUndoManager предоставляет два метода для регистрации действий отмены. Если у селектора отмены только один аргумент, можно использовать registerUndoWithTarget:selector:object. Для более сложной обработки отмены мы могли бы прибегнуть к registerUndoWithTarget:handler:.

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

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