OC与JS 交互在APP设计中越来越重要,不管是现在比较流行的Hybrid 还是 JSPatch,都会涉及到OC与JS的数据交互问题,下面总结一下主要的交互实现方式。
现阶段主要是会使用的技术是:
- 1、使用UIWebViewDelegate
- 2、第三方桥接库 WebViewJavascriptBridge (本质是使用UIWebViewDelegate)
- 3、JavaScriptCore
本文主要介绍JavaScriptCore的主要用法,WebViewJavascriptBridge的用法见其他博客
OC调用JS
stringByEvaluatingJavaScriptFromString :
只限于webView内使用,使用方法类似evaluateScript
在webView的delegate中,添加代码
1处:可以发现在加载webView中弹框
2处: 可以获取webView设置的高度,在Hybrid中可以将webView和native进行混排
JavaScriptCore
iOS 7 引入了 JavaScriptCore 库,它把 WebKit 的 JavaScript 引擎用 Objective-C 封装,提供了简单,快速以及安全的方式接入世界上最流行的语言。
最主要的意义是执行JS代码,脱离了webView的依赖。
JavaScriptCore 主要包含的功能或者组件,可以看它的头文件
JSContext (详细见.h声明)
@discussion A JSContext is a JavaScript execution environment. All
JavaScript execution takes place within a context, and all JavaScript values
are tied to a context.JS执行的环境,同时也通过JSVirtualMachine管理着所有对象的生命周期,每个JSValue都和JSContext相关联并且强引用context。
JSValue
@discussion A JSValue is a reference to a JavaScript value. Every JSValue
originates from a JSContext and holds a strong reference to it.
When a JSValue instance method creates a new JSValue, the new value
originates from the same JSContext.All JSValues values also originate from a JSVirtualMachine
(available indirectly via the context property). It is an error to pass a
JSValue to a method or property of a JSValue or JSContext originating from a
different JSVirtualMachine. Doing so will raise an Objective-C exception.JS对象在JSVirtualMachine中的一个强引用,其实就是Hybird对象。我们对JS的操作都是通过它。并且每个JSValue都是强引用一个context。同时,OC和JS对象之间的转换也是通过它。
{image} 待补充
SManagedValue
@discussion JSManagedValue represents a “conditionally retained” JSValue.
“Conditionally retained” means that as long as the JSManagedValue’s
JSValue is reachable through the JavaScript object graph,
or through the Objective-C object graph reported to the JSVirtualMachine using
addManagedReference:withOwner:, the corresponding JSValue will
be retained. However, if neither graph reaches the JSManagedValue, the
corresponding JSValue will be released and set to nil.The primary use for a JSManagedValue is to store a JSValue in an Objective-C
or Swift object that is exported to JavaScript. It is incorrect to store a JSValue
in an object that is exported to JavaScript, since doing so creates a retain cycle.JS和OC对象的内存管理辅助对象。由于JS内存管理是垃圾回收,并且JS中的对象都是强引用,而OC是引用计数。如果双方相互引用,势必会造成循环引用,而导致内存泄露。我们可以用JSManagedValue保存JSValue来避免。
JSVirtualMachine
>
@discussion An instance of JSVirtualMachine represents a single JavaScript “object space”
or set of execution resources. Thread safety is supported by locking the
virtual machine, with concurrent JavaScript execution supported by allocating
separate instances of JSVirtualMachine.
>
JS运行的虚拟机,有独立的堆空间和垃圾回收机制。
>
线程安全JSExport
@protocol
@abstract JSExport provides a declarative way to export Objective-C objects and
classes – including properties, instance methods, class methods, and
initializers – to JavaScript. 更多用法详见API,还有例子。一个协议,如果JS对象想直接调用OC对象里面的方法和属性,那么这个OC对象只要实现这个JSExport协议就可以了。
调用方法
创建一个JSContext对象,然后将JS代码加载到context里面,最后取到这个函数对象,调用callWithArguments这个方法进行参数传值。(JS里面函数也是对象)
对 JSContext 和 JSValue 实例使用下标的方式我们可以很容易地访问我们之前创建的 context 的任何值。JSContext 需要一个字符串下标,而 JSValue 允许使用字符串或整数标来得到里面的对象和数组:
JSValue 包装了一个 JavaScript 函数,我们可以从 Objective-C / Swift 代码中使用 Foundation 类型作为参数来直接调用该函数。再次,JavaScriptCore 很轻松的处理了这个桥接
|
|
- 错误处理
JSContext 还有另外一个有用的招数:通过设置上下文的 exceptionHandler 属性,你可以观察和记录语法,类型以及运行时错误。 exceptionHandler 是一个接收一个 JSContext 引用和异常本身的回调处理,可以根据信息查看上下文以及错误类型:
|
|
需要注意的是,这个赋值必须在函数调用JS函数之间,否则无法调用到,建议直接跟JSContext定义在一起
JS调用OC
WebViewJavascriptBridge
主要是注册handler,进行消息的回调,与回调block一致。
JavaScript Core
通过context,可以block和JSExport protocol 进行JS调用OC
Block
通过context,将block,转化成JS的function,通过执行这个function,调用OC里的block, 因为在JS中调用,需要注意一些桥接。
|
|
由于 block 可以保有变量引用,而且 JSContext 也强引用它所有的变量,为了避免强引用循环需要特别小心。避免保有你的 JSContext 或一个 block 里的任何 JSValue。相反,使用 [JSContext currentContext] 得到当前上下文,并把你需要的任何值用参数传递。
JSExport
自定义一个协议遵守JSExport协议,然后想要导出这些方法的类再遵守这个协议,就可以在JS中调用OC的方法
通过JSContext将类导出,即设置JS对象中与OC的一种关联
JSExportAs
Objective-C 的方法 createWithFirstName:lastName: 变成了在JavaScript中的 createWithFirstNameLastName()
OC中如果参数很多,那么写起来就很不方便,为了解决参数的问题,JSExportAs
JSExportAs(createWithFirstName,
|
|
内存管理
OC使用的ARC,JS使用的是垃圾回收机制,正常情况下,OC和JS对象之间内存管理都无需我们去关心。不过需要注意以下几点:
不要在block里面直接使用context,或者使用外部的JSValue对象,用参数进行传递
12345678910111213141516//错误代码:self.context[@"block"] = ^(){JSValue *value = [JSValue valueWithObject:@"aaa" inContext:self.context];};//一个比较隐蔽的JSValue *value = [JSValue valueWithObject:@"ssss" inContext:self.context];self.context[@"log"] = ^(){NSLog(@"%@",value);};//block捕获并持有外部的value,value强持有context和它管理的JS对象的。//正确的做法,str对象是JS那边传递过来。self.context[@"log"] = ^(NSString *str){NSLog(@"%@",str);};OC对象不要用属性直接保存JSValue对象,通过内存管理辅助对象JSManagedValue
|
|
- 不要在不同的 JSVirtualMachine 之间进行传递JS对象。
一个 JSVirtualMachine可以运行多个context,由于都是在同一个堆内存和同一个垃圾回收下,所以相互之间传值是没问题的。但是如果在不同的 JSVirtualMachine传值,垃圾回收就不知道他们之间的关系了,可能会引起异常。
线程
JavaScriptCore 线程是安全的,每个context运行的时候通过lock关联的JSVirtualMachine。如果要进行并发操作,可以创建多个JSVirtualMachine实例进行操作。
获得webView的context
|
|
这个方法是苹果私有属性,
这边要注意的是每个页面加载完都是一个新的context,但是都是同一个JSVirtualMachine。如果JS调用OC方法进行操作UI的时候,请注意线程是不是主线程。