swiftmacosnsundomanager

NSUndoManager casting NSUndoManagerProxy crash in Swift code


In our application we're using the following code:

let lInvocationTarget = lUndoManager.prepare(withInvocationTarget: self)
let _ = (lInvocationTarget as! MyObjectType).myMethod(_: self.opacity, undoManager: lUndoManager)

This compiles without warnings and runs fine under macOS 10.12 Sierra. However it crashes at runtime on 10.9 - 10.11 (Mavericks till El Capitan). The crash report notices:

Could not cast value of type 'NSUndoManagerProxy' (0x7fff76d6d9e8) to 'MyObjectType' (0x108b82218).

Then I rewrote the code to:

if let lInvocationTarget = lUndoManager.prepare(withInvocationTarget: self) as? MyObjectType {
    let _ = lInvocationTarget.setOpacity(_: self.opacity, undoManager: lUndoManager)
}

Then it doesn't crash, but then Undo is not working at all. This last way of writing comes directly out of Apples documentation, so apparently behaviour changed in Swift 3 or 10.12 SDK. I'm using Xcode 8.2 with Swift 3 and SDK 10.12

The registerUndo(withTarget, selector:, object:) is not suitable because I have a lot of other undoable methods with more arguments. Really don't want to wrap those in a dictionary orso. And even when selectors are pretty type safe nowadays, I still don't like them. There is also the block based API (registerUndo(withTarget: handler:) ), but that is unfortunately only for 10.11+.

Anyone encountered the same problem? And more important: anyone come up with a way out?


Solution

  • I am so much astonished that I hear your first code works on macOS 10.12. The method prepare(withInvocationTarget:) has been a this-hardly-works-in-Swift thing. Not only the returned proxy object is not the instance of the original class, but neither the object is not a descendent of NSObject (at lease in some former OS Xs).

    Anyway, this is one thing worth trying:

    let lInvocationTarget = lUndoManager.prepare(withInvocationTarget: self)
    _ = (lInvocationTarget as AnyObject).myMethod(self.opacity, undoManager: lUndoManager)