Диспетчер отмены на основе вызовов в Swift

У меня возникли проблемы с внедрением более сложного подхода к отмене регистрации в Swift, основанного на вызовах (на основе статьи NSHipster здесь Документы Apple по-прежнему содержат весь пример кода на Objective-C, и семантика для настройки вызова сильно отличается).

Мой NSDocument подкласс Document имеет следующий метод, который работает с объектами модели, которые я хочу сделать невыполнимыми:

func rename(object: Any, to newName: String) {
    // This is basically a protocol that requires implementing:
    // var name: String { get set }
    //  
    guard var namedObject = object as? EditorHierarchyDisplayable else {
        return
    }

    // Register undo:
    let undoController = undoManager?.prepare(withInvocationTarget: self) as? Document
    undoController?.rename(object: namedObject, to: namedObject.name)
    undoManager?.setActionName("Rename \(namedObject.localizedClassName)")

    // Perform the change:
    namedObject.name = newName
}

Я обнаружил, что undoController выше равно nil, потому что попытка приведения к Document не удалась. Если я удалю приведение (и закомментирую вызов undoController.rename(...), prepare(withInvocationTarget:) вернет следующий объект:

(lldb) print undoController
(Any?) $R0 = some {
 payload_data_0 = 0x00006080000398a0
 payload_data_1 = 0x0000000000000000
 payload_data_2 = 0x0000000000000000
 instance_type = 0x000060800024f0d8
}
(lldb) print undoController.debugDescription
(String) $R1 = "Optional(NSUndoManagerProxy)"
(lldb) 

Что я упустил?


person Nicolas Miari    schedule 21.08.2017    source источник
comment
Документация prepare(withInvocationTarget:) говорит, что возвращает self. self это undoManager. В нижней части статьи NSHipster написано, что в этой статье используется Swift версии 1.0.   -  person Willeke    schedule 21.08.2017
comment
Да и документы тоже. Но он возвращает возвращаемое значение к as ViewController (я предполагаю, что в моем случае это становится Document). Кроме того, as становится as? в Swift 2+.   -  person Nicolas Miari    schedule 21.08.2017
comment
Преобразование NSUndoManager в Document неверно. Swift 1 было все равно, но Swift 3 отказывается это делать.   -  person Willeke    schedule 21.08.2017
comment
Итак, как мне тогда вызвать мой Document метод rename(object:to:) на возвращенном прокси? Objective-C позволяет отправлять любое сообщение любому объекту (по крайней мере, во время компиляции), но Swift строго типизирован...   -  person Nicolas Miari    schedule 21.08.2017
comment
Сообщение в блоге имеет эту строку кода: let undoController : ViewController = undoManager?.prepareWithInvocationTarget(self) as ViewController. Как это перевести на Swift 3?   -  person Nicolas Miari    schedule 21.08.2017
comment
Как зарегистрировать NSUndoManager в Swift?   -  person Willeke    schedule 21.08.2017
comment
Да, я видел этот вопрос и уже думал об использовании нового API registerUndoWithTarget<TargetType>(target: TargetType, handler: TargetType -> ()). Спасибо.   -  person Nicolas Miari    schedule 21.08.2017


Ответы (1)


Я думаю, основная путаница заключается в том, что prepare(withInvocationTarget:) возвращает прокси-объект (это сам менеджер отмены, но это деталь реализации). Идея состоит в том, что вы отправляете этому прокси-объекту те же сообщения, которые вы отправляете для отмены действия, но вместо их выполнения (поскольку это не фактический объект) он внутренне фиксирует эти вызовы и сохраняет их на потом.

Итак, ваш код должен начинаться примерно так:

let selfProxy: Any = undoManager?.prepare(withInvocationTarget: self)

Это прекрасно работает в Objective-C, потому что тип "catchall" (id) имеет очень слабую проверку типов. Но эквивалентный класс Any в Swift гораздо более строгий и не поддается той же технике, если вообще поддается.

См. раздел Использование NSUndoManager и .prepare(withInvocationTarget:) в Swift 3.

person James Bucanek    schedule 25.08.2017
comment
К моему удивлению, метод, использованный в ответе, который я указал как дублирующий источник, т. е. (target as AnyObject).methodOfMyTargetClass() делает компиляцию без ошибок или предупреждений, даже если target является AnyObject, не MyClass. Я думал, что строгая система типов Swift не позволит ей скомпилироваться. В любом случае, я перешел на более новый, основанный на закрытии registerUndo(withTarget:handler:). - person Nicolas Miari; 25.08.2017
comment
Это прекрасно работает в Objective-C, потому что универсальный тип (id) имеет очень слабую проверку типов. - действительно; Раньше я использовал NSUndoManager в коде Objective-C. - person Nicolas Miari; 25.08.2017
comment
По какой-то причине я пропустил вопрос/ответ, который вы связали; благодарю вас. - person Nicolas Miari; 25.08.2017