Bloodline's Blog Notes and thoughts from Bloodline

JavaScriptCore框架实战

Comments

用以下的交互方式,实现了与Android的接口统一。

原理:熟悉Android的Webview的同学应该知道,Android只要在需要调用的类中使用@JavascriptInterface注解方法,然后webview中添加JavascriptInterface(webView.addJavascriptInterface(jsInterface, "jsObj");),便可以在js端直接使用jsObj.method调用java中定义的方法了。所以我用<JSExport>的方式,将jsObj和页面中的jsObj关联(self.context[@"jsObj"] = jsObj;),然后就基本实现了与Android一样的js交互方式。具体看下面的代码:

MyJSInterface.h

#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>

@protocol MyJSInterfaceExport <JSExport>

- (void)log:(NSString *)logStr;

- (void)myJSInterfaceMethodWithArg1:(NSString *)arg1;

/**
 *  多个参数调用时比较别扭,第二个参数之前直接加冒号。
 */
- (void)myJSInterfaceMethodWithArgs:(NSString *)arg1 :(NSString *)arg2;

- (NSNumber *)myJSInterfaceMethod:(NSNumber *)num;

//- (void)initMethodWithArg:(NSNumber *)num;

/**
 * init开头的方法必须使用JSExportAs转换。
 */
JSExportAs(initMethodWithArg, - (void)setupMethodWithArg:(NSNumber *)num);

@end

@interface MyJSInterface : NSObject <MyJSInterfaceExport>

@end

MyJSInterface.m

#import "MyJSInterface.h"

@implementation MyJSInterface

#pragma mark - js调用接口
- (void)log:(NSString *)logStr {
    NSLog(@"%@", logStr);
}

- (void)myJSInterfaceMethodWithArg1:(NSString *)arg1 {
    NSLog(@"myJSInterfaceMethodWithArg1:%@", arg1);
}

- (void)myJSInterfaceMethodWithArgs:(NSString *)arg1 :(NSString *)arg2 {
    NSLog(@"myJSInterfaceMethodWithArgs:%@, %@", arg1, arg2);
}

- (NSNumber *)myJSInterfaceMethod:(NSNumber *)num {
    int numInt = [num intValue];
    numInt += 24;
    return [NSNumber numberWithInt:numInt];
}

- (void)setupMethodWithArg:(NSNumber *)num {
    NSLog(@"call js initMethodWithArg...");
}

@end
  • MyJSInterface实现所定义的MyJSInterfaceExport 协议,然后在MyJSInterface.m中实现相应的方法。

  • 注意开头的方法时需要使用JSExportAs进行方法名的转换。当然,js和java对应OC别扭的方法名也可以通过JSExportAs来转换成比较习惯的方法。

WebViewController.m

#import "WebViewController.h"
#import <Masonry/Masonry.h>
#import <JavaScriptCore/JavaScriptCore.h>
#import "MyJSInterface.h"

@interface WebViewController () <UIWebViewDelegate>

@property (weak, nonatomic) UIWebView *webView;

@property (nonatomic, strong) JSContext *context;
@property (nonatomic, strong) JSValue   *jsObj;
@property (nonatomic, strong) JSManagedValue *managedValue;

@end

@implementation WebViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIWebView *webView = [[UIWebView alloc] init];
    //去掉UIWebView的底图
    for (UIView *subView in [webView subviews]) {
        if ([subView isKindOfClass:[UIScrollView class]]) {
            ((UIScrollView *)subView).bounces = NO;
        }
    }
    //设置背景透明
    webView.backgroundColor = [UIColor clearColor];
    webView.opaque = NO;
    self.webView = webView;
    [self.view insertSubview:webView atIndex:0];
    self.webView.delegate = self;
    
    [webView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.insets(UIEdgeInsetsMake(0, 0, 0, 0));
    }];
    
//    mainBundle下的html貌似无法使用loadHTMLString来加载
    NSURL *htmlFile = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"test" ofType:@"html"] isDirectory:NO];
    [webView loadRequest:[NSURLRequest requestWithURL:htmlFile]];

}

#pragma mark - UIWebViewDelegate
- (void)webViewDidStartLoad:(UIWebView *)webView{
    NSLog(@"webViewDidStartLoad");
    
    [self setupJSObj];
}

- (void)setupJSObj {
    self.context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    
    MyJSInterface *ocObj = [[MyJSInterface alloc] init];
    JSValue *jsObj = [JSValue valueWithObject:ocObj inContext:self.context];
    self.jsObj = jsObj;
    JSManagedValue* managedValue = [JSManagedValue managedValueWithValue:self.jsObj];
    self.managedValue = managedValue;
    [self.context.virtualMachine addManagedReference:self.managedValue withOwner:self];
    self.context[@"jsObj"] = jsObj;
    self.context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
        NSLog(@"%@", exception);
        context.exception = exception;
    };
}

- (IBAction)callJSMethod:(UIButton *)sender {
    [self.webView stringByEvaluatingJavaScriptFromString:@"log('call js method!')"];
    
}
@end
  • JSContext不能直接创建,需要从webview获取,JSContext *context = [webview valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

  • 调试时可以定义- (void)log:(NSString *)logStr;方法,然后在js文件中调用jsObj.log来进行调试。当然也可以把console.log()替换为log方法,不过要准备好铺天盖地的输出信息。

加载html时注意:

  • 添加资源文件时使用Create folder references,此时文件夹会变为蓝色。

  • loadRequestloadHTMLString都是可以加载mainbundle下的html和对应的js及css资源的,如果不能的话,则查看html是否为utf-8格式,以及上面的那一点。当然,使用上面那种加载资源的方式,要记得添加相对路径。

NSURL *htmlFile =  [[NSBundle mainBundle] URLForResource:@"text" withExtension:@"html" subdirectory:@"question/q_text"];
[questionWebView loadRequest:[NSURLRequest requestWithURL:htmlFile]];

代码:

文章中的代码都可以从我的GitHub JavaScriptCoreDemo找到。

参考资料:

wiki-JavaScriptCore