JavaScript和Native的交互

iOS Dev难免会遇上需要和JavaScript交互的时候,我是这么做的。

JavaScriptCore

JSC是webkit的一部分,主要是对JS进行解析和提供执行环境,并且,他是开源的。
JavaScriptCore源码
在iOS7推出后,JSC极大的方便了开发者对js的操作。在此之前通用的方法都是是用webview里面的一个函数stringByEvaluatingJavaScriptFromString,JS 对Native的回调都是通过拦截URL的方式进行的。

JSC中和我们相关的类就大概只有一下5个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef JavaScriptCore_h
#define JavaScriptCore_h
#include <JavaScriptCore/JavaScript.h>
#include <JavaScriptCore/JSStringRefCF.h>
#if defined(__OBJC__) && JSC_OBJC_API_ENABLED
#import "JSContext.h"
#import "JSValue.h"
#import "JSManagedValue.h"
#import "JSVirtualMachine.h"
#import "JSExport.h"
#endif
#endif /* JavaScriptCore_h */

JSContext

JS执行的环境,同时也通过JSVirtualMachine管理着所有对象的生命周期,每个JSValue都和JSContext相关联并且强引用context。

JSValue

JS对象在JSVirtualMachine中的一个强引用,其实就是Hybird对象。我们对JS的操作都是通过它。并且每个JSValue都是强引用一个context。同时,OC和JS对象之间的转换也是通过它,相应的类型转换如下:

Obj-C type JS type
nil undefined
NSNull null
NSString string
NSNumber number,boolean
NSDictionary Object object
NSArray Object object
NSDate Date object
NSBlock Function object
id Wrapper object
Class Constructor object

JSManagedValue

JS和OC对象的内存管理辅助对象。由于JS内存管理是垃圾回收,并且JS中的对象都是强引用,而OC是引用计数。如果双方相互引用,势必会造成循环引用,而导致内存泄露。我们可以用JSManagedValue保存JSValue来避免。

JSVirtualMachine

JS运行的虚拟机,有独立的堆空间和垃圾回收机制。

JSExport

一个协议,如果JS对象想直接调用OC对象里面的方法和属性,那么这个OC对象只要实现这个JSExport协议就可以了。

交互过程

分别是OC -> JS 、 JS -> OC

首先在工程中引入JSC。

JS -> OC

这种情况的使用的应该是最多的, 可能你在一个H5页面中要跳转到native页面等等。

我写了一个Demo,并写了一个相关的html代码。

步骤:

1、定义一个协议PsJsObjcDelegate

1
2
@protocol PsJsObjcDelegate <JSExport>
@end

2、定义一个遵守这个协议的Object JsObjcBridge

1
2
3
4
5
@interface JsObjcBridge : NSObject <PsJsObjcDelegate>
@property (nonatomic, weak) JSContext *jsContext;
@property (nonatomic, weak) UIWebView *webView;
@property (nonatomic, copy) void (^pSJsObjcModelBlock)(NSInteger type , NSArray *param);
@end

因为我选择了通过block来进行这个class 和 vc之间的调用,所以我定义了一个blockproperty来进行这个对象和vc之间的调用。

3、协议方法 (注意这里的方法名要和js中的一致)

1
2
- (void)jsCallNativeWithParam:(NSDictionary *)param;//无返回值
- (NSString *)jsCallNativeWithParamWithReturn:(NSDictionary *)param;//有返回值

4、在 JsObjcBridge中实现这个方法

1
2
3
- (void)jsCallNativeWithParam:(NSDictionary *)param{
self.pSJsObjcBridgeBlock(param);
}

5、把那个`.html`文件导入工程

6、在Controller中编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#pragma mark - UIWebViewDelegate
- (void)webViewDidFinishLoad:(UIWebView *)webView {
self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
JsObjcBridge *model = [[JsObjcBridge alloc] init];
self.jsContext[@"JSInteractive"] = model;
model.jsContext = self.jsContext;
model.webView = self.webView;
model.pSJsObjcBridgeBlock = ^(NSDictionary *param) {
NSLog(@"%@", param[@"vc"]);
};
model.pSJsObjcBridgeBlockWithReturn = ^(NSDictionary *param) {
return param[@"vc"];
};
self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
context.exception = exceptionValue;
NSLog(@"异常信息:%@", exceptionValue);
};
}

OC -> JS

一种是向js 中注入新的js代码、一种是调用js中已有方法。

由于我实在想不到好的应用场景,所以就只能在一个导航栏中左右分别放两个item

1
2
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"callJS" style:UIBarButtonItemStylePlain target:self action:@selector(objcCallJsFunction)];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"injectJS" style:UIBarButtonItemStylePlain target:self action:@selector(objcInjectJsFunction)];
1
2
3
4
5
6
7
8
- (void)objcCallJsFunction{
[self.jsContext[@"jsFunc"] callWithArguments:nil];
}
- (void)objcInjectJsFunction{
NSString *js = @"function add(a,b) {alert(a+b)}";
[self.jsContext evaluateScript:js];
[self.jsContext[@"add"] callWithArguments:@[@2,@3]];
}

当然你要调用或者注入的js function 是有返回值的那么上面的一句话就应该改成这样了

1
JSValue *n = [self.jsContext[@"add"] callWithArguments:@[@2,@3]];

内存管理

写博文的时候查阅相关资料发现这里还是可能会出现一些循环引用的问题。然后简单整理了一下。

  1. OC 的 ARC 和 JS的 GC
  2. 正常情况下GC都会打破循环引用

特殊情况:

  • 不要在block里面直接使用context,或者使用外部的JSValue对象。
  • OC对象不要用属性直接保存JSValue对象,因为这样太容易循环引用了。
  • 不要在不同的 JSVirtualMachine 之间进行传递JS对象。

结束的话

不多说了,只是简单的实现了一下,肯定还是有很多的东西没有考虑到,以后遇到了再完善吧。demo地址。在写这个博客的过程中思考了许多,其实在具体场景中还是有很多东西没有想到。而且在代码中调用的方法什么的卸写在一个plist里面,把各种规则放进去,这样更利于维护,和与sever的交互。这些可能都需要去思考的问题。原谅我比较low,并没有在项目中有过这样的应用。吾将上下而求索…

CepheusSun wechat
订阅我的公众号,每次更新我都不一定会告诉你!
坚持原创技术分享,您的支持将鼓励我继续创作!
0%