JSCoreBridge 0.1.1

JSCoreBridge 0.1.1

测试已测试
Lang语言 Obj-CObjective C
许可证 MIT
发布最新发布2017 年 2 月

iPhuan 维护。



  • 作者
  • iPhuan

JSCoreBridge 是基于 iOS 平台的 Apache Cordova 开源框架修改。Cordova 通过插件的方式作为桥梁实现了 Web 与 Native 之间的通信,而 JSCoreBridge 则在其基础上进行了精简修改(移除平时不常用的类和方法),改写了传统的通信机制。在保留 Cordova 实用的功能前提下,JSCoreBridge 优化了框架的大小并且减少了复杂的工程设置选项,还有效地提高了通信效率。JSCoreBridge 开源框架致力于为开发者提供更便捷的 Hybrid 开发体验。

目录



适用范围

  • 适用于 Hybrid 开发者,希望通过 JSCoreBridge 框架实现客户端 Web 与 Native 之间的交互与通信。
  • 适用于已经在使用 Cordova 框架并考虑更换 Cordova 框架的开发者。

JSCoreBridge 是在 Cordova 基础上修改的,它兼容大部分 Cordova 的用法,熟悉 Cordova 的开发者可以轻松上手。


通信原理

Cordova 通信原理:

  1. Web 创建自定义 scheme “gap://ready”,并响应链接跳转事件;
  2. Cordova 通过 WebView 代理方法 webView:shouldStartLoadWithRequest:navigationType: 截获该 gap 跳转;
  3. Cordova 通过 WebView 的 stringByEvaluatingJavaScriptFromString: 方法执行 Cordova JS 方法 nativeFetchMessages 获取 Web 当前的命令参数并转化为 CDVInvokedUrlCommand 对象;
  4. Cordova 根据对象的 classNamemethodName 属性找到对应插件和对应的插件方法,并执行插件方法;
  5. Cordova 执行完插件方法后如需给 Web 返回数据结果,则再次通过 WebView 的 stringByEvaluatingJavaScriptFromString: 方法执行 Cordova JS 方法 nativeCallback,通过 CDVInvokedUrlCommandcallbackId 作为标识将结果发送给 Web 对应的回调。


JSCoreBridge 通信原理:

不再使用传统的 scheme 链接跳转截取和 stringByEvaluatingJavaScriptFromString: 执行 JS 的方法,通过 iOS7 新增的 JavaScriptCore.framework 来实现 JS 和 Native 之间的通信。

  1. Web 调用 jsCoreBridge.jsexec 或者 execSync 方法直接将命令参数传给客户端;
  2. JSCoreBridge 将命令参数转化为 JSCInvokedPluginCommand 对象;
  3. JSCoreBridge 根据对象的 classNamemethodName 属性找到对应插件和对应的插件方法,并执行插件方法;
  4. JSCoreBridge 执行完插件方法后如需给 Web 返回数据结果,直接调用 jsCoreBridge.jsnativeCallback 方法,通过 JSCInvokedPluginCommandcallbackId 作为标识将结果发送给 Web 对应的回调。


如何获取 JSCoreBridge

  1. 直接在 GitHub 上 获取
  2. 通过 CocoaPods 将其添加到工程中:
  • 如果你想使用完整版的 JSCoreBridge,请将以下命令行添加到 Podfile 中:
    pod 'JSCoreBridge'
  • 如果你想使用精简版的 JSCoreBridge,请将以下命令行添加到 Podfile 中:
    pod 'JSCoreBridge/JSCoreBridgeLite'
  • 如果你想使用更兼容 Cordova 用法的 JSCoreBridge,请将以下命令行添加到 Podfile 中:
    pod 'JSCCordova'

或者

    pod 'JSCCordova/JSCCordovaLite'

注:Lite版的JSCoreBridge将不使用config.xml进行功能选项配置,JSCoreBridgeLite只实现了最基本的通信;如果你准备使用JSCCordova,更多详细说明请参考Cordova用法兼容性


使用说明

JSCoreBridge框架可以通过CocoaPods Pod集成到工程,也可以手动下载源码添加,添加JSCoreBridge后,只需简单配置config.xml和jsCoreBridge.js即可使用,如果框架是手动添加的,则需要添加JavaScriptCore.framework库。

config.xmljsCoreBridge.js的相关说明将在下文中详细阐述。

JSCoreBridge Demo中有JSCoreBridge的详细使用样例代码,可下载参考。


JSCoreBridge Web平台

jsCoreBridge.js存放说明:

  • jsCoreBridge.js本身在工程中,如果打开的html文件在bundle中,可直接引用;当然,如果你的html文件在bundle的子目录下,你希望jsCoreBridge.js和你的网页目录在同一级,你也可以将jsCoreBridge.js复制到该同级目录;
  • 如果你的html文件存储在沙盒中,请务必将jsCoreBridge.js复制到沙盒中;
  • 如果你的网页在远程网站上,那么你同样需要将jsCoreBridge.js放到你的远程网站上;

jsCoreBridge.js的使用原则是,保证你的html文件能够引用到。


jsCoreBridge.js接口说明:

jsCoreBridge.js对应于Cordova的cordova.js,通过jsCoreBridge对象来调用,也兼容Cordova用法,可以通过cordova对象调用,jsCoreBridge接口如下:

  • jsCoreBridge.version

获取当前JSCoreBridge Web平台JS版本号。
客户端JSCoreBridge框架对jsCoreBridge.js有最低版本要求,通过CocoaPods Pod到工程的jsCoreBridge.js相对于当前客户端JSCoreBridge框架都是最新的版本,可放心使用;如果你自行从其他途径下载jsCoreBridge.js,请确保该版本能够兼容客户端JSCoreBridge框架。

  • jsCoreBridge.exec

执行客户端对应插件方法。
通过该方法,可以告诉客户端JSCoreBridge框架通过对应插件的对应方法去执行相应的事情,代码示例如下:

    var params = {title: 'JSCoreBridge Demo'};

    jsCoreBridge.exec(function (res) {
    // 成功回调
    }, function (err) {
    // 失败回调
    }, 'JSCTestPlugin', 'changeNavTitle', [params]);
  • 第一个函数为成功回调,第二个函数为失败回调,通过res和err获取结果数据;当然,如果你不想收到回调,这两个参数可以传递空,如果你不希望接收res和err结果数据,回调函数你也可以不带参数;
  • JSCTestPlugin为客户端对应的插件Plugin类名;
  • changeNavTitleJSCTestPlugin插件中对应的插件方法;
  • 最后一个参数则为Web传给客户端的参数,通过数组的方式传递,至于数组里面传递什么样的数据,由开发者自行决定;当然该参数你也可以传递空或者不传递。
  • jsCoreBridge.execSync

同步执行客户端对应插件方法。
与exec接口不同的是,该方法为同步操作,没有成功与失败回调函数,其他参数与exec用法一致,其代码示例如下:

    var version = jsCoreBridge.execSync('JSCTestPlugin', 'getAppVersionSync', null);
  • deviceready

JSCoreBridge运行环境已准备就绪监听事件。
以下示例代码可以用来监听JSCoreBridge准备完成:

    document.addEventListener('deviceready', onDeviceReady, false)

⚠️注意:
为了保证客户端插件方法能够正确执行,请在deviceready回调中或者回调执行后调用jsCoreBridge对象的方法;
如果你在deviceready回调中调用jsCoreBridge.exec,不要期望客户端对应插件方法会在jsCoreBridgeDidReady:之前调用,jsCoreBridge.exec为异步操作,除非你使用jsCoreBridge.execSync同步方法。


  • pause

客户端已经进入后台监听事件。
以下示例代码可以用来监听客户端已经进入后台:

    document.addEventListener('pause', onPause, false)
  • resume

客户端即将进入前台监听事件。
以下示例代码可以用来监听客户端即将进入前台:

    document.addEventListener('resume', onResume, false)


JSCoreBridge Native平台

config.xml:

在Cordova中,config.xml是框架功能选项的配置文件,包含工程的一些信息,插件白名单,Web URL白名单,WebView属性设置等。同样的,在JSCoreBridge中,我们也将config.xml移植了过来,并对一些配置选项进行了删减,以便达到一个轻量级的JSCoreBridge框架。

config.xml文件并不是必须的,当你使用JSCoreBridgeLite时,将不再使用config.xml文件来配置框架;当然,你也可以通过设置JSCWebViewController类的configEnabled属性来关闭使用config.xml,以使用一个最轻量化的JSCoreBridge。
JSCoreBridge在未使用config.xml的状况下,其仅仅满足Web与Native之间通信的基本功能,不进行插件白名单验证,不进行Web URL白名单验证,并且WebView的相关属性都保持为系统默认状态。

想要了解如何配置config.xml文件,可以进一步点击这里,进入Cordova官方网站进行了解。
当然,对于一般的开发者来说,JSCoreBridge中的config.xml样例已足够满足需求,你只需配置插件白名单即可,配置示例如下:

    <feature name="JSCTestBasePlugin">
    <param name="ios-package" value="JSCTestBasePlugin" />
    <param name="onload" value="true" />
    </feature>

一般来说,保持feature中的name的值和param中的value值一致,当然你也可以不一致,但必须保证param中的value值和对应的插件类名一致;如果希望插件在JSCoreBridge初始化时就加载,可以通过<param name="onload" value="true" />来设置,如果不需要,可以省略该行。

在JSCoreBridge中,以下配置选项目前尚未实现:

  1. content
  2. access
  3. engine
  4. plugin
  5. variable
  6. preference中 (BackupWebStorageTopActivityIndicatorErrorUrlOverrideUserAgentAppendUserAgenttarget-devicedeployment-targetCordovaWebViewEngineSuppressesLongPressGestureSuppresses3DTouchGesture)

在JSCoreBridge中,以下配置选项不再需要添加:

  1. widget中 (idversiondefaultlocaleios-CFBundleVersionxmlnsxmlns:cdv)
  2. name
  3. description
  4. author

⚠️如工程用到config.xml,请在JSCoreBridge/Optional目录下将config.xml复制到其他目录并添加到工程中使用;


JSCWebViewController:

JSCWebViewController是JSCoreBridge框架直接提供给开发者使用的ViewController,可以直接使用,也可以根据需求进行继承使用,其部分API说明如下:

  • bridgeDelegate

JSCoreBridge代理。可以通过这个对象执行相应的代理方法,具体请参考JSCBridgeDelegate

  • configFilePath

config.xml文件路径。默认为nil,从 Bundle 根目录获取,如果设置了该属性,则从该属性路径获取,不支持网络地址。

  • configEnabled

是否启用config配置功能。默认启用,如需关闭,可设置为NO;当使用JSCoreBridgeLite时,configEnabled属性不可用,始终为关闭状态。

  • shouldAutoLoadURL

是否自动加载URL。默认自动加载通过initWithUrl:初始化的URL,设置为NO将关闭自动加载。

  • - (instancetype)initWithUrl:(NSString *)url

通过字符串连接初始化URL。可以在JSCWebViewController子类中重写该方法。

  • - (void)loadURL:(NSURL *)URL
  • - (void)loadHTMLString:(NSString *)htmlString

通过调用以上两个方法进行网页的手动加载。

  • - (void)jsCoreBridgeWillReady:(UIWebView *)webView
  • - (void)jsCoreBridgeDidReady:(UIWebView *)webView

JSCoreBridge将要准备就绪和已准备就绪的回调。分别在deviceready通知回调执行之前和之后调用,方便开发者在这两个时刻进行相应的操作,可以在JSCWebViewController子类中重写该方法使用。

⚠️ 特别提示:关于客户端Native和Web的相应回调方法的执行顺序请参考网页加载回调执行顺序说明


JSCBridgeDelegate:

JSCBridgeDelegate是JSCoreBridge类的代理,可以通过这个代理向Web发送结果数据,执行JS等。该代理作为JSCWebViewControllerJSCPlugin的属性来使用。

  • - (void)registerPlugin:(JSCPlugin *)plugin withPluginName:(NSString *)pluginName

将已存在的插件通过类名注册到插件白名单中。如果使用config.xml,那么JSCoreBridge将只会识别config.xml配置好的插件白名单,不在白名单范围内的插件将不会被加载和使用,可以通过该方法将插件注册到白名单中。

  • - (nullable __kindof JSCPlugin *)getPluginInstance:(NSString *)pluginName

通过插件类名来获取插件对象。可以通过该方法获取对应Plugin的实例对象。

  • - (void)sendPluginResult:(JSCPluginResult *)result forCallbackId:(NSString *)callbackId

向Web发送结果数据。将结果数据以JSCPluginResult对象实例进行封装,并以callbackId作为回调标识发送给Web。代码示例如下:

    NSDictionary *message = @{@"resCode":@"0", @"resMsg":@"OK"};
    // 将要返回给Web的结果以字典形式通过JSCPluginResult初始化
    JSCPluginResult *result = [JSCPluginResult resultWithStatus:JSCCommandStatus_OK messageAsDictionary:message];
    // 发送结果
    [self.bridgeDelegate sendPluginResult:result forCallbackId:command.callbackId];
  • - (JSValue *)evaluateScript:(NSString *)script
  • - (JSValue *)callScriptFunction:(NSString *)funcName withArguments:(nullable NSArray *)arguments

执行JS和调用JS函数方法。方法一实际通过JSContextevaluateScript:方法执行JS;方法二通过JSContextcallWithArguments:调用JS函数,需要传递函数名称funcName,如果该函数直属于Window对象,则直接传递函数名,如果该函数并不直属于Window对象,则可以通过键值路径的方式调用,代码示例如下:

    [self.bridgeDelegate callScriptFunction:@"jsCoreBridge.fireDocumentEvent" withArguments:@[@"deviceready"]];

其中jsCoreBridge必须为Window的属性。

  • - (void)onMainThreadEvaluateScript:(NSString *)script
  • - (void)onMainThreadCallScriptFunction:(NSString *)funcName withArguments:(nullable NSArray *)arguments

与上述两个方法作用一致,只是这两个方法确保执行JS和调用JS函数在主线程上。在特定情况下不在主线程上执行JS可能导致程序崩溃,通过这两个方法可以解决这个问题。

  • - (void)runInBackground:(void (^)(void))block
  • - (void)runOnMainThread:(void (^)(void))block

辅助类方法,方便开发者在后台或者主线程上处理对应的事情。


JSCPlugin:

JSCPlugin就是我们所说的插件,这是一个基类,开发者需要根据需求来分类建立多个插件,而这些插件都应当继承于JSCPlugin来使用。
JSCPlugin插件方法的声明示例如下:

    - (void)changeNavTitle:(JSCInvokedPluginCommand *)command;
    - (void)sendEmail:(JSCInvokedPluginCommand *)command;
    - (NSString *)getAppVersionSync:(JSCInvokedPluginCommand *)command; // 同步操作

接收一个JSCInvokedPluginCommand对象,JSCoreBridge支持同步操作,如果需要使用同步操作,需要对应有一个返回值,而该返回值必须是一个Object对象,否则将给Web返回空的结果数据。

JSCPlugin的部分API说明如下:

  • webView
  • webViewController

获取当前webViewwebViewController

  • backupCallbackId

用于备份某个插件方法的callbackId。当你在某个插件方法中使用了某个对象,而该对象的一些操作需要在代理方法中获取结果时,可以通过该属性来保存当前插件方法的回调callbackId,以便在代理回调中继续使用该callbackId来发送结果数据。具体用法可参考JSCoreBridge Demo
当然,该用法只适用于当前插件只有一个插件方法需要使用backupCallbackId,如果多个插件方法需要保存callbackId,建议参考cordova官方插件的一些写法,将callbackId作为其对应使用对象的属性成员,如CDVCamera插件,CDVCameraPicker继承UIImagePickerController,并拥有callbackId属性。

  • - (void)pluginDidInitialize

插件初始化后回调该方法,可以在该方法中进行一些初始化的操作,类似于UIViewControllerviewDidLoad方法。

  • - (void)canCallPlugin

JSCoreBridge调用插件时,会先通过该方法进行权限验证,如果返回YES,则可以正常调用插件,如果返回NO,则无法调用。开发者可以通过该方法进行一些权限的条件设置。


JSCPluginResult:

JSCoreBridge给Web发送的结果数据通过JSCPluginResult对象进行封装,以字符串、数组、Cordova特定的格式等多种数据格式进行发送。

  • status

结果状态,当JSCCommandStatus_OK时将结果发送给成功回调,当JSCCommandStatus_ERROR时将结果发送给失败回调。

  • keepCallback

是否需要继续回调。默认为NO,同一个callbackId只能发送一次结果数据,设为YES,则支持多次回调。例如写一个监听客户端某个按钮点击事件的插件方法,用户点击按钮一次,给Web发送一次结果消息,这种使用场景就需要将keepCallback设置为YES才能保证多次回调。


JSCInvokedPluginCommand:

JSCoreBridge通过JSCInvokedPluginCommand对象将Web发送给Native的命令参数进行封装,其属性包括如下成员:

  • callbackId
  • className
  • methodName
  • arguments

分别为回调的callbackId标识、插件类名、插件方法名、Web传给客户端的参数,JSCoreBridge正是通过这些属性来完成Web交给Native的任务。


其他框架类:

对于框架的其他类,默认为私有状态,建议开发者不要随意调用或修改,在使用框架的过程中如遇任何问题和bug欢迎联系本人沟通商讨解决。


自定义错误信息:

JSCoreBridge在以下三种情况下默认会以键resCoderesMsg给Web返回对应的code码和错误信息:

  • 插件找不到时返回错误信息字典
    @{@"resCode": @"4001", @"resMsg": @"ERROR: Plugin 'PluginName' not found, or is not a JSCPlugin. Check your plugin mapping in config.xml."}

出现这种情况的原因可能是Web传错了插件名,或者客户端没有对应的插件,或者使用了config.xml但白名单中没有添加该插件。

  • 插件无法调用时返回错误信息字典
    @{@"resCode": @"4002", @"resMsg": @"ERROR: Plugin 'PluginName' can not be called, it is not allowed."}

这种情况出现的原因在于插件方法canCallPlugin的返回值为NO。

  • 插件方法找不到时返回错误信息字典
    @{@"resCode": @"4003", @"resMsg": @"ERROR: Method 'MethodName' not defined in Plugin 'PluginName'."}

出现这种情况的原因可能是Web传错了插件方法名,或者客户端没有对应的插件方法。


开发者可以通过定义以下宏来自定义JSCoreBridge给Web返回的错误信息的键和值,代码示例:

    #define JSC_KEY_RESCODE @"errCode"
    #define JSC_KEY_RESMSG @"errMsg"

    #define JSC_RESCODE_PLUGIN_NOT_FOUND @"401"
    #define JSC_RESCODE_PLUGIN_CANNOT_CALL @"402"
    #define JSC_RESCODE_METHOD_NOT_DEFINED @"403"

    #define JSC_RESMSG_PLUGIN_NOT_FOUND  @"ERROR: Plugin not found, or is not a JSCPlugin. Check your plugin mapping in config.xml."
    #define JSC_RESMSG_PLUGIN_CANNOT_CALL  @"ERROR: Plugin can not be called, it is not allowed."
    #define JSC_RESMSG_METHOD_NOT_DEFINED  @"ERROR: Method not defined in Plugin."  

在返回成功和失败结果数据时,建议开发者通过code和message的形式给Web返回结果信息,以便Web开发者能够通过code和message识别当前情况或问题所在。


网页加载回调执行顺序说明

关于JSCoreBridge加载网页时,Web和Native对应回调方法的执行顺序,这里需要特别说明:

  • 如果jsCoreBridge.js在html页面直接引用,如下所示:
    <script type="text/javascript" src="jscorebridge.js"></script>

各个回调的执行顺序如下:

  1. window.onload

    如果你在Web中写了该方法,将在此刻执行。

  2. load

    Web监听Window load事件的回调,如window.addEventListener("load", jscWindowOnLoad, false)。如果你在Web中写了该方法,将在此刻执行。

  3. jsCoreBridgeWebViewDidFinishLoad

    JSCWebViewController类中的回调方法,实际上是WebViewwebViewDidFinishLoad:代理方法。

  4. jsCoreBridgeWillReady

    JSCWebViewController类中JSCoreBridge即将准备就绪时的回调

  5. deviceready

    Web监听JSCoreBridge已准备就绪的通知回调

  6. jsCoreBridgeDidReady

    JSCWebViewController类中JSCoreBridge准备就绪之后的回调


  • 如果jsCoreBridge.js是通过别的JS通过appendChild的方式加入,如下所示:
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = 'jscorebridge/jscorebridge.js';
    var head = document.getElementsByTagName('head')[0];
    head.appendChild(script);

各个回调的执行顺序如下:

  1. jsCoreBridgeWebViewDidFinishLoad
  2. window.onload
  3. jscWindowOnLoadload
  4. jsCoreBridgeWillReady
  5. deviceready
  6. jsCoreBridgeDidReady

与第一种情况不同的是,当jsCoreBridge.js通过代码的方式加入后,WebView不会等待jsCoreBridge.js加载完再回调webViewDidFinishLoad:
通过测试可以发现,所有JS在window.onload或者load监听事件回调中就已经加载完毕。如果是第二种执行顺序,实现上JSCoreBridge选择在load监听事件回调里发送deviceready通知。不选择window.onload的原因在于,如果客户端向JSContext中添加window.onload方法,那么对于Web开发人员来说,如果他再写了window.onload方法,该方法将不再调用。
对于第一种执行顺序,JSCoreBridge是直接在webViewDidFinishLoad:代理方法中发送deviceready通知,因为此时load监听事件回调已完成,在webViewDidFinishLoad:中可以直接获取到jsCoreBridge.jsjsCoreBridge对象。

开发者可参考以上两种情况的执行顺序来决定自己在开发中如何在各个回调中处理相应的事情。


Cordova用法兼容性

JSCoreBridge基于Cordova修改,无论是Web平台还是Native平台,都保留了其原始的使用方法:

  • 在Web平台,依然可以通过cordova.exec(successFuntion, failFuntion, 'pluginName', 'methodName', [params])方式调用,同时新增jsCoreBridge对象调用的方式,新增jsCoreBridge.execSync同步方法。
  • 在Native平台,config.xml配置方式与Cordova一致,只是删减了部分配置选项。Plugin插件方法的编写也保持与Cordova一致。新增同步插件方法,唯一的区别在于各个相关联的类名都对应修改成JSCoreBridge框架的类,并在实现上可能稍作修改。
  • 如果你想要使用更兼容Cordova用法的JSCoreBridge,可以使用0.1.1版本中新增的JSCCordova。JSCCordova并不包含在JSCoreBridge Pod库中,需单独Pod JSCCordova库。JSCCordova的目的是最大化地去兼容Cordova的用法。如果开发者已经在使用Cordova,想将Cordova替换为JSCoreBridge而不想修改太多源代码,JSCCordova此时就发挥了其很大的作用。当然,需要说明的一点是,使用JSCCordova并不能保证你的代码完全不用修改。由于JSCoreBridge在实现上的差异,开发者在使用Cordova API的差异,以及本身JSCCordova也没有完全兼容Cordova所有接口等原因,开发者在使用JSCCordova时,可能会出现相应的报错,但这些报错是可以通过解决来避免的。


⚠️ 风险声明

  • JSCoreBridge框架通过KVC的方式[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"],从webView中获取JSContext,有涉嫌使用苹果私有API的风险,尽管该方法在网上被大量应用而未遭到苹果拒绝,但本人无法保证能够100%通过审核,如果您对这些问题有所顾虑,请评估风险后再使用。

    JSCoreBridge会持续跟进和更新,之后有更好的实现方法,会第一时间来屏蔽这个风险。

  • 尽管本框架已经进行过多次自测,但还未进行大范围的测试,可能会存在未知的bug,如果在使用本框架时,因为未知bug导致的风险需要您自行承担。

    欢迎使用本框架的各方反馈在使用中遇到的各种问题和bug。


开源说明

JSCoreBridge框架是基于我深入了解Apache Cordova后在其基础上修改和封装的,本着开源的思想,现上传至GitHub,并提供CocoaPods支持,后续会持续跟进更新。如果你在使用本框架,欢迎及时反馈你在使用过程中遇到的各种问题和bug,也欢迎大家与我沟通和分享更多互联网技术。iPhuan其他开源资源将会不定期的更新至iPhuanLib


如何联系我

邮箱:[email protected]
QQ:519310392

添加QQ时请备注JSCoreBridge




版本更新记录

V0.1.0

更新日期:2017年2月18日
更新说明:

  • 发布JSCoreBridge第一个版本。

V0.1.1

更新日期:2017年2月19日
更新说明:

  • 新增JSCCordova,JSCCordova有助于JSCoreBridge更好地兼容Cordova用法。

文档相应介绍:点击查看