笔记:iOS 与 JavaScript 的交互(二)JavaScriptCore


 拍摄于 2017-04-08,练车。总觉得上衣这边的口袋放包烟,能有种老司机的风范。
拍摄于 2017-04-08,练车。总觉得上衣这边的口袋放包烟,能有种老司机的风范。

JavaScriptCore

基本概念

  1. JavaScriptCore: iOS 7 开始在 iOS 中引入的一个 JavaScript 虚拟机,一个 WebKit 运行环境,这个具体的虚拟机类叫做 JSVirtualMachine,一般开发情况下不会直接对这个类进行操作;
  2. JSContext: 一个运行环境、像是前端开发中面对的浏览器,但同时并不存在 window、或者任何浏览器提供的 DOMBOM 对象;
  3. JSValue: 「一个用来处理 iOS 中所有可能存在的 JavaScript 执行后产生 Value 的类」。比如使用 JS 返回一串字符 return str,那么返回回来的就不是 iOS 的 String 而是一个 JSValue ,需要转换成 iOS 原生的 String,就需要调用原生相关的 JSValue.toString() 方法(不是 JavaScript 中的 String.prototype.toString)。
  1. 返回的数值可以通过 toNumber()toDouble()toInt32()toUInt32() 方法转换成 iOS 中对应的值;
  2. 返回的则对应 toArray() 方法来进行 JSValue 到 native value 的转化。

通过 JavaScriptCore 将 Markdown 转换成 HTML

通过 JavaScriptCore 将 Markdown 转换成 HTML
找资料的时候这篇 JavaScriptCore in Swift 中有很多不错的例子,贴一个「使用 JavaScriptCore 在 Xcode Playground 中通过 JavaScript 第三方库转换 markdown 到 html 」的例子到这里,希望可以帮助对 JavaScriptCore 可以做什么加深感受

首先在 Xcode 里新建一个 .playground 文件,然后去这里Markdown.Converter.js 下载到本地,拖拽到 .playground 左侧的 /Resource 文件夹下,之后在 .playground 文件中通过以下来引入到当前需要使用的 JavaScript 运行环境、也就是 JSContext 中。

加载

接下来通过 context.evaluateScript(script) 方法来执行,在 iOS 环境内、通过 JavaScript 将 Markdown 转换成 HTML 的任务:将 #Hello World 转换成 <h1>Hello World</h1>

在没有浏览器的情况下,原生直接执行 JavaScript

JSContext.playground
以上两个例子其实都是 native 通过 JavaScriptCore 中调用 JavaScript,这部分的内容对在没有 UIWebView 的情况下,native 和 JavaScript 的相互调用做个归纳。

原生通过 evaluateScript(_:) 执行 JavaScript

和上文的「比较简单的例子」一样


JavaScript 调用原生


调用引入的 JavaScript

与以上直接执行 JavaScript 语句对应,也可以从外部引入 JavaScript 到当前的环境,进行对不同 JavaScript 的调用。 比方说有如下 JavaScript :

与文章开头的第二个例子一样,首先需要将以上 JavaScript 引入到 playground,在 playground 左侧的 Resources 文件夹下新建一个 common.js、将其导入:

- Playground
  └ Resources
    └ common.js

之后通过 evaluateScript() 对 JavaScript 方法进行调用:


通过下标方式对 JavaScript 方法进行获取和调用

同样以上文中导入的 sayHello(name) 为例,原生也可以通过 objectForKeyedSubscript(_:)call(withArguments:) 来获取或者调用1

以上是单独使用的 JavaScriptCoreJSContext) 的一些方法,接下来是结合 UIWebViewJavaScriptCore 一起的列子。

JSExport

JSExport 是一个协议:

The protocol you implement to export Objective-C classes and their instance methods, class methods, and properties to JavaScript code。

大致的意思是: 如果想要将一个自定义 class 里的方法暴露给外部的 JavaScript 使用,那么这个类就必须 实现 JSExport 这个协议。原文中提到了这是给 Objective-C 使用的协议,实际操作中也可以使用 @objc 来转换 Swift 语言。

比如,如果我们想要定义一个具有 sayHello() 方法的 class,而且这个方法可以被外部的 JavaScript 调用,首先,我们需要定义一个 protocol(注意前缀 @objc 就是上文提到的转化),在其中包含需要暴露出去的 sayHello() 方法:

第一步。

第二步,定义我们需要的 class,继承上面我们定义的协议,然后定义我们需要的 sayHello() 方法。

这里在 Swift 这里为了方便演示,定义的是一个 static func,也就是 class 可以直接调用的方法,所以直接把 class 暴露出去,也可以定义一般方法,实例新的 class 对象,将对象暴露出去。

第三步暴露,有点类似之前 JavaScriptCore 的例子,使用 JSContext 对象的相关方法进行操作,桥接原生和 JavaScript。

接下来就可以通过 JavaScript 来执行调用:

第四步调用。需要注意的是在 JavaScript 中,如果需要传入参数来调用原生方法,在 JavaScript 的调用中须要把原生方法的参数名(named parameter)以驼峰(camelCase)的方式拼接

比如给上面定义的 PersonJavaScritMethod 协议多添加一个方法:

调用的时候就需要这么写 JavaScript:


JSContext 和 UIWebView

除了 JSContent 直接实例之外,还可以通过调用 UIWebViewvalueForKeyPath 方法来获得。

UIWebView 中通过 valueForKeyPath 来获得 context

大部分时候通过以上这个方法获得 context 都需要在 UIWebView 相关的 UIWebViewDelegate 中的 web​View​Did​Finish​Load(_:​) 进行实现。

返回的 context 作为原生和 JavaScript 之间的桥来使用,既可以让原生调用到 JavaScript,也可以使 context 中的 JavaScript 获得调用原生的功能(这应该算是使用 UIWebView 和 JavaScript 进行混合开发的一般原理之一)。

具体的相互调用和上面 JavaScriptCoreUIWebView 中提到的方法并无区别。


JSExport 和 UIWebView

这种其实也和上文中提到的 JSExport 那部分对应,只是通过别的自定义类来继承 JSExport 协议,详见下面的演示。

演示 Demo

因为 JavaScriptCore 的特殊,所有实际上的有两种实现,分别使用 JSContextJSExport。两者的录屏一模一样,如果下载了演示文件、运行,前者在第二个 tab 而后者在第三个。

JSContext 和 UIWebView

JSContextUIWebView

主要还是在于需要在 webViewDidFinishLoad() 这个 delegate 之后进行具体操作:

对应的 JavaScript 中的调用:


JSExport 和 UIWebView

JSExportUIWebView

首先定义 JSExport 协议,之后在当前的 ViewController 的继承,具体的暴露 JSBridge 要到 webViewDidFinishLoad delegate 之中,关键步骤如下:

具体到 JavaScript 中的调用:

遇到的问题

JavaScript 的命名空间须要暴露

前端中常使用 (function(win, doc){ // ... code}(window, document)) 或者 $(function() {}) 来避免污染全局的命名空间 JavaScriptCore native 调用 JavaScript 中需要将方法或者将一个包含方法的对象赋值到 window 对象上:


iOS 的 UI 须要在主线程处理

iOS 中,在调用 alert() 方法将 JavaScript 传过来的 input.value 弹出的时候,有一个需要将当前线程切换到 main thread 的问题,不是非常了解,只能引用 Z姐的原话:

GCDrunloop 相关的东西如果你不想仔细研究的话,先记下更新 UI 相关的事情需要在主线程中(main thread)处理就好。

参考


  1. 完全脱离浏览器(UIWebView)仅使用 JSContext 来处理数据也可以参考 Ray 家这篇 JavaScriptCore Tutorial for iOS: Getting Started 的前两部分:Invoking JavaScript Methods 和 Exposing Native Code。文中通过 iOS native 的 URLSessionshared.dataTask() 来获取 JSON 数据,接着将数据传递给 JavaScript,通过 map()filter() 对数据做一些处理,完成后再传递给 native 去操作构建集合。 ↩︎

感谢阅读

你们好, 2018 年初把小站从 Jekyll 迁移到 Hugo 的过程中,删除了评论区放的 Disqus 插件,考虑有二:首先无论评论、还是对笔记内容的进一步讨论,读者们更喜欢通过邮件、或者 Twitter 私信的方式来沟通;其次一年多以来 Disqus 后台能看到几乎都是垃圾留言(spam),所以这里直接贴一下邮件、以及 Twitter 账户 地址。

技术发展迭代很快,所以这些笔记内容也有类似新闻的时效性,不免有过时、或者错误的地方,欢迎指正 ^_^。

BEST
Lien(A.K.A 胡椒)
本站总访问量 本站总访客量 本文总阅读量