Skip to content

Instantly share code, notes, and snippets.

@keith
Created July 20, 2017 07:17
Show Gist options
  • Save keith/a0388486714ca91c27e1848b2f6b8306 to your computer and use it in GitHub Desktop.
Save keith/a0388486714ca91c27e1848b2f6b8306 to your computer and use it in GitHub Desktop.
NSDocument canClose in Swift
/// So, lets talk about AppKit. The way this function works is by passing a delegate (which is required
/// to be a NSObject, but here is typed as Any) which turns out to be the same type as `self`, a Selector,
/// which in this case is `_something:didSomething:soContinue:` (not kidding), and some "contextInfo"
/// (which is actually a block). While all of these arguments appear to be optional, passing nil through
/// to the function call that `shouldCloseSelector` defines, or passing nil to super, will cause the app
/// to crash. So then we need to call the function on `delegate` defined by `shouldCloseSelector`.
///
/// According to the documentation the function signature for this selector looks like this:
/// - (void)document:(NSDocument *)doc shouldClose:(BOOL)shouldClose contextInfo:(void *)contextInfo
///
/// So of course since this is an instance method on NSDocument (or in theory whatever type `delegate` is)
/// we also have the implicit Objective-C runtime arguments of the object and the selector. Using the
/// knowledge of the type of this function, we can query the Objective-C runtime in order to get the
/// function pointer to the implementation, and then cast that into our custom typealias that we create
/// so that we can call it from Swift. In order to be overly cautious I've treated all these values as
/// optional, even though they "shouldn't be".
override func canClose(withDelegate delegate: Any, shouldClose shouldCloseSelector: Selector?,
contextInfo: UnsafeMutableRawPointer?)
{
if self.fileURL != nil {
super.canClose(withDelegate: delegate, shouldClose: shouldCloseSelector, contextInfo: contextInfo)
return
}
typealias EverythingIsHorribleAndYouShouldBeSadAboutThat = @convention(c)
(NSObject, Selector, NSDocument, Bool, UnsafeMutableRawPointer) -> Void
guard let selector = shouldCloseSelector, let context = contextInfo,
let object = delegate as? NSObject, let objcClass = objc_getClass(object.className) as? AnyClass,
let method = class_getMethodImplementation(objcClass, selector) else
{
super.canClose(withDelegate: delegate, shouldClose: shouldCloseSelector, contextInfo: contextInfo)
return
}
let function = unsafeBitCast(method, to: EverythingIsHorribleAndYouShouldBeSadAboutThat.self)
function(object, selector, self, true, context)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment