You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
DSBridge-Swift is a [DSBridge-iOS](https://github.com/wendux/DSBridge-IOS) fork in Swift. It allows developers to send method calls back and forth between Swift and JavaScript.
12
14
15
+
# Installation
16
+
17
+
DSBridge is available on both iOS and Android.
18
+
19
+
This repo is a pure Swift version. You can integrate it with Swift Package Manager.
20
+
21
+
> It's totally OK to use Swift Package Manager together with CocoaPods or other tools. If Swift Package Manager is banned, use [the original Objective-C version DSBridge-iOS](https://github.com/wendux/DSBridge-IOS).
22
+
23
+
For Android, see [DSBridge-Android](https://github.com/wendux/DSBridge-Android).
First of all, use `DSBridge.WebView` instead of `WKWebView`:
15
42
```swift
16
43
importclassDSBridge.WebView
@@ -23,23 +50,31 @@ class ViewController: UIViewController {
23
50
}
24
51
```
25
52
26
-
DSBridge-Swift does not rely on Objective-C runtime. Thus you can declare your interface with pure Swift types:
53
+
Declare your `Interface` with the `@Exposed` annotation. All the functions will be exposed to JavaScript:
27
54
```swift
28
-
29
55
importFoundation
30
56
importtypealiasDSBridge.Exposed
31
57
importprotocolDSBridge.ExposedInterface
32
58
33
59
@Exposed
34
60
classMyInterface {
35
-
funcreturnValue() ->Int { 101 }
61
+
funcaddingOne(toinput: Int) ->Int {
62
+
input +1
63
+
}
64
+
}
65
+
```
66
+
For functions you do not want to expose, add `@unexposed` to it:
67
+
68
+
```swift
69
+
@Exposed
70
+
classMyInterface {
36
71
@unexposed
37
72
funclocalMethod()
38
73
}
39
74
```
40
-
Mark your interface `@Exposed` and that's it. Add `@unexposed` annotation to any function you don't want to expose.
41
75
42
-
If you don't need to declare it as a class, why not use a struct? Or, even further, an enum!
76
+
Aside from `class`, you can declare your `Interface` in `struct` or `enum`:
77
+
43
78
```swift
44
79
@Exposed
45
80
enumEnumInterface {
@@ -57,45 +92,53 @@ enum EnumInterface {
57
92
}
58
93
```
59
94
60
-
You then add your interfaces into `DSBridge.WebView`, with or without a namespace:
95
+
You then add your interfaces into `DSBridge.WebView`.
96
+
97
+
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.
98
+
61
99
```swift
62
-
webView.addInterface(Interface(), by: nil) // `nil` works the same as ""
100
+
webView.addInterface(MyInterface(), by: nil) // `nil` works the same as ""
As you can see, there is a empty string returned. The response we sent in the interface is printed by the `function`.
91
132
92
-
OK, we send async response with the completion in its first parameter. What does the second parameter, the `Bool` do then?
133
+
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:
93
134
94
-
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:
What will happen if we remove the `Bool` from the completion, you might ask. It won't compile. It might shock you how rough the `Exposed` macro is implemented if you click the Xcode error.
126
168
# Declaration Rules
127
169
## Allowed Interface Types
128
170
You can declare your interface as these types:
@@ -134,39 +176,93 @@ You can declare your interface as these types:
134
176
## Allowed Data Types
135
177
You can receive or send the following types:
136
178
- String
137
-
- Int, Double
179
+
- Int, Double (types toll-free bridged to NSNumber)
138
180
- Bool
181
+
- Standard JSON top-level objects:
182
+
183
+
- Dictionary that's encodable
184
+
185
+
- Array that's encodable
139
186
140
-
And standard JSON top-level objects:
141
-
- Dictionary that's encodable
142
-
- Array that's encodable
143
187
144
188
## Allowed Function Declarations
189
+
DSBridge-Swift ignores argument labels and parameter names of your functions. Thus you can name your parameters whatever you want.
190
+
191
+
#### Synchronous Functions
192
+
193
+
About parameters, synchronous functions can have:
194
+
195
+
- 1 parameter, which is one of the above-mentioned *Allowed Data Types*
196
+
- no parameter
197
+
198
+
About return value, synchronous functions can have:
199
+
200
+
- return value that's one of the above-mentioned *Allowed Data Types*
201
+
- no return value
202
+
145
203
For simplicity, we use `Allowed` to represent the before-mentioned Allowed Data Types.
146
-
You can define your synchronous functions in three ways:
147
204
148
205
```swift
149
206
funcname()
150
207
funcname(Allowed)
151
208
funcname(Allowed) -> Allowed
152
209
```
153
-
You can have at most one parameter. You can name it with anything, `func name(_ input: Int)`, `func name(using input: Int)`, or whatever you want.
210
+
#### Asynchronous Functions
211
+
212
+
Asynchronous functions are allowed to have 1 or 2 parameters and no return value.
213
+
214
+
If there are 2 parameters, the first one must be one of the above-mentioned *Allowed Data Types*.
215
+
216
+
The last parameter has to be a closure that returns nothing (i.e., `Void`). For parameters, the closure can have:
217
+
218
+
- 1 parameter, one of the above-mentioned *Allowed Data Types*
219
+
- 2 parameters, the first one is one of the above-mentioned *Allowed Data Types* and the second one is a `Bool`
You can have your completion attributed with `@ecaping` if you need it to persist longer than the function call.
162
-
163
-
Like the parameter, you can name the completion whatever you like.
164
-
# JavaScript side
165
-
Check out [the original repo](https://github.com/wendux/DSBridge-IOS) for how to use the JavaScript DSBridge.
230
+
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.
166
231
167
232
# Differences with DSBridge-iOS
233
+
234
+
## Seamless `WKWebView` Experience
235
+
236
+
When using the old DSBridge-iOS, in order to implement `WKWebView.uiDelegate`, you'd have to set `dsuiDelegate` instead. In DSBridge-Swift, you can just set `uiDelegate`.
237
+
238
+
The old `dsuiDelegate` does not respond to new APIs, such as one that's released on iOS 16.4:
239
+
240
+
```swift
241
+
@available(iOS16.4, *)
242
+
funcwebView(
243
+
_webView: WKWebView,
244
+
willPresentEditMenuWithAnimatoranimator: any UIEditMenuInteractionAnimating
245
+
) {
246
+
247
+
}
248
+
```
249
+
250
+
Even if your `dsuiDelegate` does implement it, it won't get called on text selections or editing menu animations. The reason is that the old DSBridge-iOS relay those API calls to you by implementing them ahead of time and calling `dsuiDelegate` inside those implementations. This causes it to suffer from iOS iterations. Especially that it crashes when it tries to use the deprecated `UIAlertView`.
251
+
252
+
DSBridge-Swift, instead, makes better use of iOS Runtime features to avoid standing between you and the web view. You can set the `uiDelegate` to your own object just like what you do with bare `WKWebView` and all the delegation methods will work as if DSBridge is not there.
253
+
254
+
On the contrary, you'd have to do the dialog thing yourself. And all the dialog related APIs are removed, along with the `dsuiDelegate`.
255
+
256
+
## Static instead of Dynamic
257
+
258
+
When using the old DSBridge-iOS, your *JavaScript Object* has to be an `NSObject` subclass. Functions in it have to be prefixed with `@objc`. DSBridge-Swift, however, is much more Swift-ish. You can use pure Swift types like `class` or even `struct` and `enum`.
259
+
260
+
## Customizable
261
+
262
+
DSBridge-Swift provides highly customizable flexibility which allows you to change almost any part of it. You can even extends it to use it with another piece of completely different JavaScript. See section *Open / Close Principle* below.
263
+
168
264
## API Changes
169
-
### Newly added:
265
+
### Newly added
170
266
A new calling method that allows you to specify the expected return type and returns a `Result<T, Error>` instead of an `Any`.
171
267
```swift
172
268
call<T>(
@@ -176,62 +272,49 @@ call<T>(
176
272
completion: @escaping (Result<T, any Swift.Error>) ->Void
177
273
)
178
274
```
179
-
### Renamed:
275
+
### Renamed
180
276
-`callHandler` is renamed to `call`
181
277
-`setJavascriptCloseWindowListener` to `dismissalHandler`
182
278
-`addJavascriptObject` to `addInterface`
183
279
-`removeJavascriptObject` to `removeInterface`
184
280
185
-
### Removed:
186
-
`loadUrl(_: String)` is removed. Define your own one if you need it.
187
-
188
-
`onMessage`, as a private method marked public, is removed.
189
-
190
-
The old DSBridge-iOS relay all the `WKUIDelegate` calls for you, which cause it to suffer from iOS iteration. Especially that it crashes when it tries to show the deprecated `UIAlertView`.
281
+
### Removed
282
+
-`loadUrl(_: String)` is removed. Define your own one if you need it
191
283
192
-
DSBridge-Swift, instead, makes better use of iOS Runtime features to avoid standing between you and the web view. You can set the `uiDelegate` to your own object just like what you do with the bare `WKWebView`.
284
+
-`onMessage`, a public method that's supposed to be private, is removed
193
285
194
-
On the contrary, you'd have to do the dialog thing yourself. And all the dialog related APIs are removed, along with the `dsuiDelegate`:
195
286
-`dsuiDelegate`
196
287
-`disableJavascriptDialogBlock`
197
288
-`customJavascriptDialogLabelTitles`
198
289
- and all the `WKUIDelegate` implementations
199
-
##Other minor differences
290
+
### Not Implemented
200
291
201
-
- Does not require `NSObjectProtocol` for interfaces and `@objc` for functions.
202
292
- Debug mode not implemented yet.
203
293
204
-
# Customization
294
+
# Open / Close Principle
205
295
206
-
DSBridge-Swift really shines on how it allows you to customize it.
207
-
## Resolving Incoming Calls
208
-
This is how a synchronous method call comes in and returns back:
296
+
DSBridge-Swift has a Keystone that holds everything together.
297
+
298
+
> A keystone is a stone at the top of an arch, which keeps the other stones in place by its weight and position. -- Collins Dictionary
299
+
300
+
Here is how a synchronous method call comes in and returns back:
The `Keystone` converts raw text into an `Invocation`. You can change how it resolves raw text by changing `methodResolver` or `jsonSerializer` of `WebView.keystone`.
Your own jsonSerializer has to implement a two-method protocol `JSONSerializing`. Keep in mind that DSBridge needs it to encode the response into JSON on the way back. It's really ease, though, and you can then use SwiftyJSON or whatever you want:
221
-
```swift
222
-
structMyJSONSerializer: JSONSerializing {
223
-
funcreadParamters(
224
-
fromtext: JSON?
225
-
) throws-> IncomingInvocation.Signature {
226
-
227
-
}
228
-
funcserialize(_object: Any) throws-> JSON {
229
-
230
-
}
231
-
}
232
-
```
315
+
There might be something you don't want in the built-in JSON serializer. For example it won't log details about an object or text in production environment. You can change this behavior by defining your own errors instead of using the ones defined in `DSBridge.Error.JSON`.
233
316
234
-
`methodResolver: any MethodResolving` is even easier, it's a one-method protocol. You just read a text and return a `Method`:
317
+
`methodResolver` is even easier. It simply reads a text and finds the namespace and method name:
To explain to you how we can customize JavaScript evaluation, here's how an asynchronous invocation works.
335
+
336
+
Everything is the same before the invocation reaches the dispatcher. The dispatcher returns an empty response immediately after it gets the invocation, so that the webpage gets to continue running. From now on, the synchronous chain breaks.
An empty response is returned immediately when it reaches the dispatcher. After that, the `Dispatcher` continues to dispatch the method call:
340
+
Dispatcher sends the invocation to `Interface` at the same time. But since the way back no longer exists, DSBridge-Swift has to send the repsonse by evaluating JavaScript:
After the interface returned, the data is wrapped into an `AsyncResponse` and delivered to the JavaScript evaluator.
344
+
The `JavaScriptEvaluator` is in charge of all the messages towards JavaScript, including method calls initiated from native. The default evaluator evaluates JavaScript every 50ms to avoid getting dropped by iOS for evaluating too frequently.
260
345
261
-
Guess what, you can substitute it with your own.
346
+
If you need further optimization or you just want the vanilla experience instead, you can simply replace the `Keystone.javaScriptEvaluator`.
262
347
263
348
## Keystone
264
-
As you can see from all above, the keystone is what holds everything together.
265
-
266
-
> A keystone is a stone at the top of an arch, which [keeps](https://www.collinsdictionary.com/dictionary/english/keep"Definition of keeps") the other stones in place by its [weight](https://www.collinsdictionary.com/dictionary/english/weight"Definition of weight") and position. -- Collins Dictionary
267
349
268
-
You can change the keystone, with either a `Keystone` subclass or a completely different `KeystoneProtocol`. Either way, you will be able to use DSBridge-Swift with any JavaScript.
350
+
As you can see from all above, the keystone is what holds everything together. You can even change the keystone, with either a `Keystone` subclass or a completely different `KeystoneProtocol`. Either way, you will be able to use DSBridge-Swift with any JavaScript.
0 commit comments