Rename selectors exported to JavaScriptCore in Swift
JavaScript engines running in your mobile apps are very powerful tools. Where they shine is when you can integrate them with your native code and make them cooperate.
In Objective-C, there is a convention that is followed when renaming selectors to JavaScript function names. Colons are removed, and the character following them is capitalized. doFoo:withBar:
is exported as doFooWithBar
.
If you wanted or needed to use a different name, you could use a JSExportAs
macro in your protocol definition.
1 2 3 4 5 6 7 8 9 10 11 |
@protocol BloggingProtocol <JSExport> // in JavaScript: publishBlogPostWithAuthor() - (void)publishBlogPost:(JSValue*)blogPost withAuthor:(JSValue*)author // in JavaScript: publish() JSExportAs(publish, - (void)publishBlogPost:(JSValue*)blogPost withAuthor:(JSValue*)author ); @end |
This feature is missing in Swift, and automatic conversion of Objective-C methods defined in Swift (with @objc
prefix) results in parameter names and labels being appended to the function name.
1 2 3 4 |
@objc protocol BloggingProtocol: JSExport { // in JavaScript: publishWithBlogPostWith() func publish(blogPost: JSValue, with author: JSValue) } |
Most often, it will not be what you want it to be.
Luckily, there is a simple workaround. To customize the name, you can use a block stored in the property of an object since there is no difference – from JavaScriptCore point of view – between a method and a block stored in the property of an object. In JavaScript, it’s like defining an anonymous function on an object’s property instead of defining a function on the class.
An extra type alias has been defined to make snippets easier to read, but this is not necessary.
1 2 3 4 5 6 |
typealias PublishClosure = (@convention(block) (JSValue?, JSValue?) -> void) @objc protocol BloggingProtocol: JSExport { // execute in JavaScript as: publish() var publish: PublishClosure { get } } |
Implementation of the protocol:
1 2 3 4 5 |
@objc class Blog: NSObject, BloggingProtocol { var publish: PublishClosure = { blogPost, author in return "Publishing \(blogPost!.toString() ?? "") as \(author!.toString() ?? "")." } } |
Usage:
1 2 3 4 |
let blog = Blog() let jsContext = JSContext()! jsContext.setObject(blog, forKeyedSubscript: NSString(string: "blog")) jsContext.evaluateScript("blog.publish('JSExportAs in Swift', 'Karol')") |
The proposed solution gives you the flexibility to follow any pattern for naming your exported Swift API without limiting you to JavaScriptCore’s predefined conventions.
Feedback is welcome!
Want to join the discussion?Feel free to contribute!
Leave a Reply
Want to join the discussion?Feel free to contribute!