Crashed when completionBlock passed as argument
SpectralDragon opened this issue · comments
I wanna call method beginSheetModalForWindow:completionHandler:
in NSOpenPanel object, but app crashed in Invocation.swift
file at line 139.
How to pass object?
It would be great if you post a complete example so I can reproduce the issue.
extension UIWindow {
// Works correctly =)
var nsWindow: AnyObject? {
guard let nsWindows = NSClassFromString("NSApplication")?.value(forKeyPath: "sharedApplication.windows") as? [AnyObject] else { return nil }
for nsWindow in nsWindows {
let uiWindows = nsWindow.value(forKeyPath: "uiWindows") as? [UIWindow] ?? []
if uiWindows.contains(self) { return nsWindow }
}
return nil
}
}
// button action
let panel = Dynamic.NSOpenPanel()
panel.beginSheetModalForWindow(self.view.window!.nsWindow!, completionHandler: { response in
print(response)
})
I see.
The problem here is that NSOpenPanel.beginSheetModalForWindow()
expects an Objective-C block, and we're passing a Switch closure which is entirely a different thing, and not interchangeable with Objective-C blocks.
From docs:
Block objects are a C-level syntactic and runtime feature.
And from this post:
(...) closures are a super-special case, like a custom type, built in Swift. Where in Objective-C, all of them are Objective-C objects. So to bridge Swift closure into Objective-C, Swift needs to create an Objective-C object for you
I can think of two options here:
- Use
panel.runModal()
, and implementNSOpenSavePanelDelegate.validateURL
to know when the user presses the "Open" button. - Create an Objective-C class that calls the method for you, and Swift will handle the bridging between ObjC blocks and Swift closures.
BTW, the extension can be re-written as following:
extension UIWindow {
var nsWindow: NSObject? {
Dynamic.NSApplication.sharedApplication.delegate.hostWindowForUIWindow(self)
}
}
Good news! I found a way to make Swift convert the closure to an Objective-C block.
You just need to add @convention(block)
to the closure type:
typealias ResultBlock = @convention(block) (_ result: Int) -> Void
Now, this works flawlessly:
let panel = Dynamic.NSOpenPanel()
panel.beginSheetModalForWindow(self.view.window.nsWindow, completionHandler: { result in
print("result: ", result)
} as ResultBlock)
I added a section about Objective-C blocks in the readme file. Thank you for bringing this to my attention!