Created
July 20, 2017 07:17
-
-
Save keith/a0388486714ca91c27e1848b2f6b8306 to your computer and use it in GitHub Desktop.
NSDocument canClose in Swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// 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