Skip to content

JavaScript 调用 Native 接口

iMoeNya edited this page Apr 23, 2024 · 3 revisions

简介

首先,在你的视图中使用 DSBridge.WebView 而非 WKWebView

import class DSBridge.WebView
class ViewController: UIViewController {
    // ......
    override func loadView() {
        view = WebView()
    }
    // ......
}

声明一个类型并加上 @Exposed 注释,它便成了一个 Interface,其下的方法将被暴露给 JavaScript:

import Foundation
import typealias DSBridge.Exposed
import protocol DSBridge.ExposedInterface

@Exposed
class MyInterface {
    func addingOne(to input: Int) -> Int {
        input + 1
    }
}

对于不想暴露的方法,加上 @unexposed 注释:

@Exposed
class MyInterface {
    @unexposed
    func localMethod()
}

除了 class,你也可以声明 struct 或者 enum 作为 Interface

@Exposed
enum EnumInterface {
    case onStreet
    case inSchool
    
    func getName() -> String {
        switch self {
        case .onStreet:
            "Heisenberg"
        case .inSchool:
            "Walter White"
        }
    }
}

最后,将接口添加到 WebView 中。

注意,第二个参数 by 传入的是命名空间,传入 nil 或空字符串,则该 Interface 没有命名空间。同时只能有一个没有命名空间的 Interface,每个命名空间下同时也只能有一个 Interface,如果重复则后来者居上:

webView.addInterface(MyInterface(), by: nil)  // `nil` works the same as ""
webView.addInterface(EnumInterface.onStreet, by: "street")
webView.addInterface(EnumInterface.inSchool, by: "school")

之后,你就可以从 JavaScript 调用这些方法了,注意在方法名前加上命名空间:

bridge.call('addingOne', 5)  // returns 6
bridge.call('street.getName')  // returns Heisenberg
bridge.call('school.getName')  // returns Walter White

你完全可以声明多层的命名空间,如 a.b.c 等。

声明异步方法略有不同,方法的最后一个参数必须是一个闭包,你将通过这个闭包来返回你的响应:

@Exposed
class MyInterface {
    func asyncStyledFunction(callback: (String) -> Void) {
        callback("Async response")
    }
}

从 JavaScript 调用时,对应地,将回调函数传入:

bridge.call('asyncStyledFunction', function(v) { console.log(v) });
// ""
// Async response

可以看到,调用之后会立刻收到一个空字符串返回,这是符合期望的。而我们的异步返回值则是在传入的回调 function 中获得的。

DSBridge 提供了一次调用、多次返回的功能,你只需要将给闭包增加一个 Bool 类型的参数,这个参数意味着是否已完成。响应时,若传入 false,表示未完成,以后你还可以再次调用这个闭包来发送响应;若传入 true,JS 端将删除回调函数,即不再接收对于本次调用的响应:

@Exposed
class MyInterface {
    func asyncFunction(
        input: Int, 
        completion: @escaping (Int, Bool) -> Void
    ) {
        // 传入 `false` 要求 JS 保留回调函数
        completion(input + 1, false)
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            completion(input + 2, false)
        }
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            // 传入 `true` 则 JS 将删除回调函数
            completion(input + 3, true)
        }
        // 之后再调用也不会有效果了
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
            completion(input + 4, true)
        }
    }
}

JavaScript 调用:

bridge.call('asyncFunction', 1, function(v) { console.log(v) });
// ""
// 2
// 3
// 4

检查接口是否存在

你可以在 JavaScript 代码中查询 Native 是否存在某个接口:

bridge.hasNativeMethod('test')  // true

也可以指定接口的同/异步:

bridge.hasNativeMethod('test', 'syn')  // true
bridge.hasNativeMethod('test', 'asyn')  // false

Interface 声明规则

支持的 Interface 类型

你可以将 Interface 声明为 classstructenum。暂未支持 actor,欢迎大家的想法。

支持的数据类型

你可以发送或接收这些类型的数据:

  • String
  • Int, Double 等(与 NSNumber 无缝转换的类型)
  • Bool
  • 标准的 JSON 顶层对象:
    • Dictionary,必须可编码为 JSON
    • Array,必须可编码为 JSON

支持的方法声明

DSBridge-Swift 无视 Interface 中的方法的参数名,无论调用名还是内部名,因此你可以使用任意的参数名。

同步方法

关于参数,同步方法只能:

  • 有 1 个参数,类型符合上述”支持的数据类型“

  • 没有参数

关于返回值,同步方法可以:

  • 有返回值,类型符合上述”支持的数据类型“
  • 没有返回值

为了简便,使用 Allowed 代指上面说的”支持的数据类型“:

func name()
func name(Allowed)
func name(Allowed) -> Allowed

异步方法

异步方法可以有 1 个或 2 个参数,不允许有返回值。

如果有 2 个参数,第 1 个参数类型必须符合上述”支持的数据类型“。

方法的最后一个参数必须是闭包,返回 Void。关于参数,闭包只能:

  • 有 1 个参数,类型符合上述”支持的数据类型“
  • 有 2 个参数,第 1 个类型符合上述”支持的数据类型“,第 2 个必须是 Bool 类型
typealias Completion = (Allowed) -> Void
typealias RepeatableCompletion = (Allowed, Bool) -> Void

func name(Completion)
func name(RepeatableCompletion)
func name(Allowed, Completion)
func name(Allowed, RepeatableCompletion)

闭包可以是 @escaping 的;如果不是的话,请注意,你的方法应当快速执行、立即返回,否则将会阻塞主线程。