YTKJsBridge 0.1.4

YTKJsBridge 0.1.4

xiaochun0618 维护。



  • lihc

YTKJsBridge

CI Status Version License Platform

YTKJsBridge 是什么?

YTKJsBridge 是一套基于 OC 语言实现的iOS库,允许客户端与网页 JS 之间相互调用,主要依赖 JavaScriptCore、UIWebView 实现,JavaScriptCore 该 framework 在 JSPatch、RN 等中被广泛使用。

YTKJsBridge 提供了哪些功能?

  • 支持客户端向网页动态注入 OC 实现的方法,并支持命名空间。
  • 支持双向事件监听机制。
  • 支持网页以同步、异步方式调用客户端注入的 OC 方法。
  • 支持网页调用客户端注入的 OC 方法时,直接以 JSON 格式数据作为参数,无需序列化。
  • 支持客户端主动调用网页提供的 JavaScript 方法。

哪些项目适合使用 YTKJsBridge?

YTKJsBridge 适合使用 OC 实现的项目,项目中使用 UIWebView 作为网页容器,并且有较多的客户端与网页直接交互的需求。

如果你的项目中使用了 UIWebView,并且有大量的客户端与网页交互逻辑,使用 YTKJsBridge 将会极大地帮助你,简化客户端与网页交互的实现逻辑,并且交互都是同步的,与本地调用基本一样,提升交互效率。

YTKJsBridge 的基本思想

YTKJsBridge 的基本思想是将一个或多个 JS 注入方法的实现放在一个实现了类的内部,并且同一个命名空间可以注册多个方法实现类,因此使用 YTKJsBridge,您可以一或多个相关的注入方法,可以放入单独的类中管理,并支持命名空间,也就是说多个注入方法实现类可以有同名方法。

YTKJsBridge 会在网页中注入一个名为 YTKJsBridge 的全局方法,后续所有需要向 YTKJsBridge 注入的方法,都是通过这个全局方法来调用执行的,后面使用方法中有具体的例子。

同时也支持以 block 的形式作为方法实现注入 JS 方法。

将一个或多个注入方法的实现放在一个类内,有以下好处:

  • 天然会将一类相关的注入方法都封装在同一个类中管理,这样可以避免注入方法在项目各处零散分布,从而可能导致的重复代码问题。
  • 避免将所有注入方法放在一个类中实现,这样可以防止文件过长得过长,逻辑交错,增加后续维护成本。

YTKJsBridge 提供了事件监听处理的机制,在原有的 JS 方法注入基础上进行了抽象,简化了数据回传处理。

当然,它的缺点是,你的工程和网页都需要使用 YTKJsCommand 定义的 JSON 结构进行数据传递,但 YTKJsCommand 本身就定义了一个比较灵活的结构。

安装

你可以在 Podfile 中添加以下一行代码以使用 YTKJsBridge:

pod 'YTKJsBridge'

安装要求

YTKJsBridge 版本 最低 iOS Target 注意
0.1.3 iOS 7 要求 Xcode 7 及以上版本

例子

克隆当前的repo,然后在 Example 目录下执行 pod install 命令,即可运行示例工程。

数据格式说明

JS 调用客户端的数据格式如下:

{
    "methodName": "math.fib" // 方法名,math是命名空间,fib为具体方法名
    "args": [value1, value2, ...] // 参数数组
    "callId": xxx // 同步为-1, 异步为非-1
}

客户端通过调用 JS 的全局方法 dispatchCallbackFromNative 向 JS 回传数据,数据以 JSON 格式的字符串传递,数据格式如下:

{
    "callId": xxx, // 同步调用callId为-1,异步调用不是-1,用以标记回传与调用的对应关系
    "code": 0, // 非0表示失败
    "ret": object, // 回传数据,可以是任意格式,字典、数组、字符串、数字、BOOL等
    "message": "", // 错误描述
}

客户端通过调用 JS 的全局方法 dispatchNativeCall 调用 JS,数据以 JSON 格式的字符串传递,数据格式如下:

{
    "methodName": "", // js方法名
    "args": [], // 参数数组
    "callId": xxx,
}

客户端监听 JS 的事件,数据以字典的形式传递,数据格式如下:

{
    "event": "", // event名
    "arg": , // 参数,可以是任意格式,字典、数组、字符串、数字、BOOL等
}

JS 监听客户端的事件,数据以 JSON 格式的字符串传递,JSON 数据格式与 JS 事件格式一致

使用方法

向 JS 注入方法

客户端以 block 的形式向网页注入方法,例如:注入命名空间 math 下的同步方法 fib 和异步方法 asyncFib,用于计算斐波那契数列,注意:这些方法是在异步线程中执行的,具体如下

// 斐波那契数列
- (NSInteger)fibSequence:(NSInteger)n {
    if (n < 2) {
        return n == 0 ? 0 : 1;
    } else {
        return [self fibSequence:n - 1] + [self fibSequence:n -2];
    }
}

UIWebView *webView = [UIWebView new];
// webView加载代码省略...
YTKJsBridge *bridge = [[YTKWebViewJsBridge alloc] initWithWebView:webView];

// 向JS注入在命名空间math之下的同步方法fib
[bridge addSyncJsCommandName:@"fib" namespace:@"math" impBlock:^id(NSArray * _Nullable argument) {
    NSInteger n = [argument.firstObject integerValue];
    return @([self fibSequence:n]);
}];

// 向JS注入在命名空间math之下的异步方法asyncFib
[bridge addAsyncJsCommandName:@"asyncFib" namespace:@"math" impBlock:^(NSArray * _Nullable argument, YTKJsCallback block) {
    NSInteger n = [argument.firstObject integerValue];
    block(nil, @([self fibSequence:n]));
}];

为了避免客户端代码以 block 形式注入过于分散,YTKJsBridge 提供了以对象形式向 JS 注入方法的功能,并且将 JS 传递的参数展开到方法的形参中。例如:有一个带 3 个参数的方法 @selector(method:arg1:arg2)。首先需要创建一个方法实现类,以下是向网页注入在命名空间 math 下的同步 fib 和异步 asyncFib 方法示例,方法功能是计算斐波那契数列,方法接收一个参数 num 用来计算 fib,如下所示:

@interface YTKFibHandler : NSObject

@end

@implementation YTKFibHandler

// fibSequence的实现忽略,与前面demo代码实现一致
// 同步方法fib
- (NSNumber *)fib:(NSNumber *)num {
    NSInteger fib = [self fibSequence:num.integerValue];
    return @(fib);
}

// 异步方法asyncFib,带有异步方法回调completion
- (void)asyncFib:(NSNumber *)num completion:(YTKJsCallback)completion {
    NSInteger fib = [self fibSequence:num.integerValue];
    completion(nil, @(fib));
}

@end

然后客户端向网页注入该方法类即可,以下是向网页注入 YTKFibHandler 的代码示例:

UIWebView *webView = [UIWebView new];
// webView加载代码省略...
YTKJsBridge *bridge = [[YTKWebViewJsBridge alloc] initWithWebView:webView];
// 向JS注入在命名空间math之下的fib和asyncFib方法
[bridge addJsCommandHandlers:@[[YTKFibHandler new]] namespace:@"math"];

JS调用原生注入方法

下面是网页调用客户端异步执行math命名空间下的asyncFib方法的代码,客户端注入的asyncFib方法需要参数n,代码如下:

// 准备要传给客户端异步方法asyncFib的数据,包括指令,数据,回调等,
var data = {
    methodName:"math.asyncFib", // 带有命名空间的方法名
    args:[5],  // 参数
    callId:123  // callId为-1表示同步调用,否则为异步调用
};
// 直接使用这个客户端注入的全局YTKJsBridge方法调用math命名空间下的asyncFib方法执行
YTKJsBridge(data);
// 通过dispatchCallbackFromNative来接收回传数据

下面是网页调用客户端同步执行math命名空间下的fib方法的代码,客户端注入的fib方法需要参数n,代码如下:

// 准备要传给客户端同步方法fib的数据,包括指令,数据,回调等,
var data = {
    methodName:"math.fib", // 带有命名空间的方法名
    args:[8],  // 参数
    callId:-1  // callId为-1表示同步调用,否则为异步调用
};
// 直接使用这个客户端注入的全局YTKJsBridge方法调用math命名空间下的fib方法执行
var dict = YTKJsBridge(data);
var fib = dict["ret"]; // fib就是客户端返回的结果

原生调用JS方法

直接调用YTKWebViewJsBridge对象的方法即可,以下为例,客户端调用网页执行名为alert的JS方法,带有三个参数message,cancelTitle,confirmTitle,分别代表alert提示的文案、取消按钮文案、确认按钮文案,代码如下:

UIWebView *webView = [UIWebView new];
// webView加载代码省略...
YTKJsBridge *bridge = [[YTKWebViewJsBridge alloc] initWithWebView:webView];
// 准备传入JS的数据,包括指令,数据等
NSArray *parameter = @[@"hello, world", @"cancel", @"confirm"];
// 客户端调用网页的alert方法,弹出alert弹窗
[bridge callJsCommandName:@"alert" argument:parameter];

JS向原生发送事件通知

JS发送页面大小变化resize事件给原生,代码如下:

var event = {
    "event": "resize", // event名
    "arg": [width, height], // 参数
};
sendEvent(event); // sendEvent是native注入的全局函数

原生监听JS事件

以下为客户端监听JS页面大小变化resize事件的示例,代码如下:

UIWebView *webView = [UIWebView new];
// webView加载代码省略...
YTKJsBridge *bridge = [[YTKWebViewJsBridge alloc] initWithWebView:webView];

// 便捷block方式
[bridge listenEvent:@"resize" callback:^(NSArray *argument) {
    // 客户端监听js页面大小发生变化事件
}];

// 添加监听者对象id<YTKJsEventListener>的方式
[bridge addListener:self forEvent:@"resize"]
// 实现YTKJsEventListener代理方法
- (void)handleJsEventWithArgument:(NSArray *)argument {
    // 客户端监听js页面大小发生变化事件
}

原生向JS发送事件通知

以下为客户端发送click事件的示例,代码如下:

UIWebView *webView = [UIWebView new];
// webView加载代码省略...
YTKJsBridge *bridge = [[YTKWebViewJsBridge alloc] initWithWebView:webView];
[bridge notifyEvent:@"click" argument:@[@"click event"]];

JS监听原生事件

以下为JS监听客户端close事件的示例,代码如下:

// obj是native传入的event事件对象,dispatchNativeEvent是用来处理事件的全局函数
window.dispatchNativeEvent = function(obj) {
    if (obj.event == "click") {
        // 处理close事件,这里通过alert将arg显示出来
        alert(obj.arg[0]);
    }
}

作者

YTKJsBridge的主要作者是:

lihc, https://github.com/xiaochun0618

协议

YTKJsBridge 在 MIT 协议下获得授权。查阅 LICENSE 文件获取更多信息。