3

this is my very first project in swift and yes I've see that there are similar question about this topic already around and I've read almost all but I still can't get my code working.

I've a class that implements the WKScriptMessageHandler protocol to provide a bridge between iOS and the hosted javascript in a WKWebView.

Among the parameters passed from javascript (interpreted as NSDictionary), I have one named _method that contains the swift method name that javascript wants to invoke.

Metod name is extracted successfully on swift side and then I try to call it by using a Selector and here is where I fail:

class AppScriptMessageHandler: NSObject, WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        let dict = message.body as! NSDictionary;
        let method = dict.value(forKey: "_method") as? String ?? "";
        let selector = Selector(method);  // method = "getDocumentsFolder"

        let ret = perform(selector)?.takeUnretainedValue()
        print(ret!)
    }

    func getDocumentsFolder() -> String {
        return "Pippo"
    }
}

All goes fine until I try to do let ret = perform(selector). Here I get the following runtime error:

2018-02-27 15:49:37.279730+0100 wktest[8375:198645] *** Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '-[wktest.AppScriptMessageHandler getDocumentsFolder]: unrecognized selector sent to instance 0x604000019ef0'

Where I do wrong? I've also tried appending : to method but I still get same error Thanks in advance for any help!

lviggiani
  • 5,824
  • 12
  • 56
  • 89

1 Answers1

2

Try to change it to @objc func getDocumentsFolder() -> String

Swift language uses Table Dispatch for all class methods. But for performing selector on method, this method should be invoked with Message Dispatch.

That's what @objc keyword is doing - it's marks function as dynamic and it will be invoked with Message Dispatch

You can read more about method dispatches here

UPD

Agree with @Hamish comment, @objc will not make this function method dispatch for Swift, but for perform(_:) method.

Community
  • 1
  • 1
Taras Chernyshenko
  • 2,729
  • 14
  • 27
  • It works! Great, thank you! I have another question: is it correct to use the `.takeUnretainedValue()` on return value? I want it to be garbage collected after using in. Or do I have to also call the `.autorelease()` methd? – lviggiani Feb 27 '18 at 15:18
  • 1
    Ok I have found the sanwer here: https://stackoverflow.com/questions/29048826/when-to-use-takeunretainedvalue-or-takeretainedvalue-to-retrieve-unmanaged-o – lviggiani Feb 27 '18 at 15:22
  • 1
    `@objc` actually doesn't have much to do with method dispatch, it merely exposes the method to Obj-C; Swift will still call the method using table or even direct dispatch if possible. The `dynamic` attribute is what forces Swift to call a method using message dispatch. Though of course, if the method is being invoked through the Obj-C runtime, such as in this case with `perform(_:)`, message dispatch will be used. – Hamish Feb 27 '18 at 15:38