Search code examples
javascriptswiftjavascriptcore

Calling native Swift code from javascript using JavascriptCore and JSContext


I'm trying to use native Swift code in javacript that runs in a JSContext.

For example I have this class implemented in Swift:

class Greeter: NSObject {

   public func greet() -> String {
     return "Hello World!"
   }

   public func greetMe(_ name: String) -> String {
     return "Hello, " + name + "!"
   }
}

Then I use a JSContext to run a javascript code:

let context =  JSContext()!
context.setObject(Greeter.self, forKeyedSubscript: "Greeter" as (NSCopying & NSObjectProtocol))

// Try my native functions:
let jsv1 = context.evaluateScript("Greeter.greet()")!
let jsv2 = context.evaluateScript("Greeter.greetMe(\"Jon Arbuckle\")")!

print("Greeter.greet() =  \(jsv1)") // prints Greeter.greet() = undefined
print("Greeter.greatMe(\"Jon Arbuckle\") = \(jsv2)") // prints Greeter.greetMe("Jon Arbuckle") = undefined

Couldn't figure out what I'm doing wrong.


Solution

  • One thing we should not forget is to expose methods or properties by conforming to JSExport protocol.

    @objc protocol GreeterJSExports: JSExport {
       func greet() -> String
       func greetMe(_ name: String) -> String
       static func getInstance() -> Greeter
       //any other properties you may want to export to JS runtime 
       //var greetings: String {get set}
    }
    

    Conform to this protocol

    class Greeter: NSObject, GreeterJSExports {
       public func greet() -> String {
         return "Hello World!"
       }
    
       public func greetMe(_ name: String) -> String {
         return "Hello, " + name + "!"
       }
       class func getInstance() -> Greeter {
         return Greeter()
       }
    }
    

    Set the object in JSContext as usual

    let context = JSContext()
    context?.setObject(Greeter.self, forKeyedSubscript: "Greeter" as (NSCopying & NSObjectProtocol))
    let jsValue1 = context?.evaluateScript("(function(){ var greeter = Greeter.getInstance(); return greeter.greet()})()")
    let jsValue2 = context?.evaluateScript("(function(){ var greeter = Greeter.getInstance(); return greeter.greetMe('rikesh')})()")
    print(jsValue1!)
    print(jsValue2!)
    

    Remember, the above methods are instance methods. We need object to call it. Swift does not expose init(), hence, I added a method to getInstance to return an instance.