JavaScriptCore
基本概念
- JavaScriptCore: iOS 7 开始在 iOS 中引入的一个 JavaScript 虚拟机,一个 WebKit 运行环境,这个具体的虚拟机类叫做
JSVirtualMachine
,一般开发情况下不会直接对这个类进行操作; - JSContext: 一个运行环境、像是前端开发中面对的浏览器,但同时并不存在
window
、或者任何浏览器提供的DOM
、BOM
对象; - JSValue: 「一个用来处理 iOS 中所有可能存在的 JavaScript 执行后产生 Value 的类」。比如使用 JS 返回一串字符
return str
,那么返回回来的就不是 iOS 的String
而是一个JSValue
,需要转换成 iOS 原生的String
,就需要调用原生相关的JSValue.toString()
方法(不是 JavaScript 中的String.prototype.toString
)。
通过 JavaScriptCore 将 Markdown 转换成 HTML
首先在 Xcode 里新建一个 .playground
文件,然后去这里将 Markdown.Converter.js
下载到本地,拖拽到 .playground
左侧的 /Resource
文件夹下,之后在 .playground
文件中通过以下来引入到当前需要使用的 JavaScript 运行环境、也就是 JSContext
中。
接下来通过 context.evaluateScript(script)
方法来执行,在 iOS 环境内、通过 JavaScript 将 Markdown 转换成 HTML 的任务:将 #Hello World
转换成 <h1>Hello World</h1>
在没有浏览器的情况下,原生直接执行 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:
以上是单独使用的 JavaScriptCore
(JSContext
) 的一些方法,接下来是结合 UIWebView
和 JavaScriptCore
一起的列子。
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()
方法。
第三步暴露,有点类似之前 JavaScriptCore
的例子,使用 JSContext
对象的相关方法进行操作,桥接原生和 JavaScript。
接下来就可以通过 JavaScript 来执行调用:
第四步调用。需要注意的是在 JavaScript
中,如果需要传入参数来调用原生方法,在 JavaScript
的调用中须要把原生方法的参数名(named parameter
)以驼峰(camelCase
)的方式拼接。
比如给上面定义的 PersonJavaScritMethod
协议多添加一个方法:
调用的时候就需要这么写 JavaScript:
JSContext 和 UIWebView
除了 JSContent
直接实例之外,还可以通过调用 UIWebView
的 valueForKeyPath
方法来获得。
大部分时候通过以上这个方法获得 context
都需要在 UIWebView 相关的 UIWebViewDelegate 中的 webViewDidFinishLoad(_:)
进行实现。
返回的 context
作为原生和 JavaScript 之间的桥来使用,既可以让原生调用到 JavaScript,也可以使 context
中的 JavaScript 获得调用原生的功能(这应该算是使用 UIWebView
和 JavaScript 进行混合开发的一般原理之一)。
具体的相互调用和上面 JavaScriptCore
和 UIWebView
中提到的方法并无区别。
JSExport 和 UIWebView
这种其实也和上文中提到的 JSExport
那部分对应,只是通过别的自定义类来继承 JSExport
协议,详见下面的演示。
演示 Demo
因为 JavaScriptCore
的特殊,所有实际上的有两种实现,分别使用 JSContext
和 JSExport
。两者的录屏一模一样,如果下载了演示文件、运行,前者在第二个 tab 而后者在第三个。
JSContext 和 UIWebView
主要还是在于需要在 webViewDidFinishLoad()
这个 delegate 之后进行具体操作:
对应的 JavaScript 中的调用:
JSExport 和 UIWebView
首先定义 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姐的原话:
GCD
和runloop
相关的东西如果你不想仔细研究的话,先记下更新 UI 相关的事情需要在主线程中(main thread
)处理就好。
参考
- JavaScriptCore Tutorial for iOS: Getting Started
- JavaScriptCore - NSHipster(英文)
- JavaScriptCore - NSHipster(中文)
- From Swift to Javascript and Back – Swift Programming – Medium
- Calling anonymous javascript function in swift using JSContext - Stack Overflow
- JavaScriptCore REPL
- JavaScriptCore in Swift
- iOS 开发 - Swift 使用 JavaScriptCore 与 JS 交互 - 马燕龙个人博客
- Swift中UIWebView与Javascript交互解决方案 - 一叶博客
- Hybrid载体的变化(二)
完全脱离浏览器(UIWebView)仅使用 JSContext 来处理数据也可以参考 Ray 家这篇 JavaScriptCore Tutorial for iOS: Getting Started 的前两部分:Invoking JavaScript Methods 和 Exposing Native Code。文中通过 iOS native 的
URLSession
的shared.dataTask()
来获取 JSON 数据,接着将数据传递给 JavaScript,通过map()
和filter()
对数据做一些处理,完成后再传递给 native 去操作构建集合。 ↩︎
技术发展迭代很快,所以这些笔记内容也有类似新闻的时效性,不免有过时、或者错误的地方,欢迎指正 ^_^。
BEST
Lien(A.K.A 胡椒)