-
Notifications
You must be signed in to change notification settings - Fork 10
JavaScript Calling Native APIs
First of all, use DSBridge.WebView
instead of WKWebView
:
import class DSBridge.WebView
class ViewController: UIViewController {
// ......
override func loadView() {
view = WebView()
}
// ......
}
Declare your Interface
with the @Exposed
annotation. All the functions will be exposed to JavaScript:
import Foundation
import typealias DSBridge.Exposed
import protocol DSBridge.ExposedInterface
@Exposed
class MyInterface {
func addingOne(to input: Int) -> Int {
input + 1
}
}
For functions you do not want to expose, add @unexposed
to it:
@Exposed
class MyInterface {
@unexposed
func localMethod()
}
Aside from class
, you can declare your Interface
in struct
or enum
:
@Exposed
enum EnumInterface {
case onStreet
case inSchool
func getName() -> String {
switch self {
case .onStreet:
"Heisenberg"
case .inSchool:
"Walter White"
}
}
}
You then add your interfaces into DSBridge.WebView
.
The second parameter by
specifies namespace. nil
or an empty string indicates no namespace. There can be only one non-namespaced Interface
at once. Also, there can be only one Interface
under a namespace. Adding an Interface
to an existing namespace replaces the original one.
webView.addInterface(MyInterface(), by: nil) // `nil` works the same as ""
webView.addInterface(EnumInterface.onStreet, by: "street")
webView.addInterface(EnumInterface.inSchool, by: "school")
Done. You can call them from JavaScript now. Do prepend the namespace before the method names:
bridge.call('addingOne', 5) // returns 6
bridge.call('street.getName') // returns Heisenberg
bridge.call('school.getName') // returns Walter White
DSBridge supports multi-level namespaces, like
a.b.c
.
Asynchronous functions are a little bit different. You have to use a completion handler to send your response:
@Exposed
class MyInterface {
func asyncStyledFunction(callback: (String) -> Void) {
callback("Async response")
}
}
Call from JavaScript with a function accordingly:
bridge.call('asyncStyledFunction', function(v) { console.log(v) });
// ""
// Async response
As you can see, there is a empty string returned. The response we sent in the interface is printed by the function
.
DSBridge allows us to send multiple responses to a single invocation. To do so, add a Bool
parameter to your completion. The Bool
means isCompleted
semantically. If you pass in a false
, you get the chance to repeatedly call it in future. Once you call it with true
, the callback function will be deleted from the JavaScript side:
@Exposed
class MyInterface {
func asyncFunction(
input: Int,
completion: @escaping (Int, Bool) -> Void
) {
// Use `false` to ask JS to keep the callback
completion(input + 1, false)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
completion(input + 2, false)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
// `true` to ask JS to delete the callback
completion(input + 3, true)
}
// won't have any effect from now
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
completion(input + 4, true)
}
}
}
Call from JavaScript:
bridge.call('asyncFunction', 1, function(v) { console.log(v) });
// ""
// 2
// 3
// 4
You can check whether there is some API in native:
bridge.hasNativeMethod('test') // true
Specify if the API is synchronous / asynchronous:
bridge.hasNativeMethod('test', 'syn') // true
bridge.hasNativeMethod('test', 'asyn') // false
You can declare your interface as these types:
-
class
-
enum
-
struct
actors are not supported yet. Please file up your ideas about it.
You can receive or send the following types:
-
String
-
Int, Double (types toll-free bridged to NSNumber)
-
Bool
-
Standard JSON top-level objects:
-
Dictionary that's encodable
-
Array that's encodable
-
DSBridge-Swift ignores argument labels and parameter names of your functions. Thus you can name your parameters whatever you want.
About parameters, synchronous functions can have:
- 1 parameter, which is one of the above-mentioned Allowed Data Types
- no parameter
About return value, synchronous functions can have:
- return value that's one of the above-mentioned Allowed Data Types
- no return value
For simplicity, we use Allowed
to represent the before-mentioned Allowed Data Types.
func name()
func name(Allowed)
func name(Allowed) -> Allowed
Asynchronous functions are allowed to have 1 or 2 parameters and no return value.
If there are 2 parameters, the first one must be one of the above-mentioned Allowed Data Types.
The last parameter has to be a closure that returns nothing (i.e., Void
). For parameters, the closure can have:
- 1 parameter, one of the above-mentioned Allowed Data Types
- 2 parameters, the first one is one of the above-mentioned Allowed Data Types and the second one is a
Bool
typealias Completion = (Allowed) -> Void
typealias RepeatableCompletion = (Allowed, Bool) -> Void
func name(Completion)
func name(RepeatableCompletion)
func name(Allowed, Completion)
func name(Allowed, RepeatableCompletion)
Attribute your closure with @ecaping
if needed. Otherwise, keep in mind that your functions run on the main thread and try not to block it.