iOS Objective-C与Javascript交互总结

2017-01-09  本文已影响0人  倾世圣伊
一、在iOS7之前,对JavaScript操作只能使用UIWebView中一个方法stringByEvaluatingJavaScriptFromString,JavaScript对Objective-C的回调都是基于URL的拦截进行操作。
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
    self.webView.delegate = self;
    [self.view addSubview:self.webView];
    NSURL * url = [NSURL URLWithString:@"https://www.baidu.com"];
    NSURLRequest * request = [[NSURLRequest alloc] initWithURL:url];

    [self.webView loadRequest:request];
}
1.stringByEvaluatingJavaScriptFromString在UIWebView加载完成时使用,以下是简单使用场景:
#pragma mark -- UIWebViewDelegate
- (void)webViewDidFinishLoad:(UIWebView *)webView {
    //当前页面URL
    NSString * currentUrl = [webView stringByEvaluatingJavaScriptFromString:@"document.location.href"];
    //当前页面的title
    NSString * title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
    
    //填充文本输入框的内容
    [self.webView stringByEvaluatingJavaScriptFromString:@"document.getElementById('index-kw').value = 'apple';"];
    //模拟点击按钮
    [self.webView stringByEvaluatingJavaScriptFromString:@"document.getElementById('index-bn').click();"];
}
2.JavaScript对Objective-C的回调基于URL的拦截进行操作:
#pragma mark - UIWebViewDelegate
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    NSURL * url = request.URL;
    NSString * scheme = [URL scheme];
    if ([scheme isEqualToString:@"XXXX"]) {
        //do something
        return NO;
    }
    return YES;
}
二、iOS7之后苹果推出JavaScriptCore框架,该框架让Objective-C与Javascript代码的交互变得更加简单方便。
JavaScriptCore下的类和协议:
  • JSContext:JS执行的环境,同时也通过JSVirtualMachine管理着所有对象的生命周期,每个JSValue都和JSContext相关联并且强引用context。
  • JSValue:JavaScript和Object-C之间互换的桥梁。
  • JSManagedValue:JS和OC对象的内存管理辅助对象,由于JS内存管理是垃圾回收,并且JS中的对象都是强引用,而OC是引用计数。如果双方相互引用,势必会造成循环引用,而导致内存泄露。我们可以用JSManagedValue保存JSValue来避免。
  • JSVirtualMachine:JS运行的虚拟机,有独立的堆空间和垃圾回收机制,处理线程相关,使用较少。
  • JSExport:一个协议,如果JS对象想直接调用OC对象里面的方法和属性,那么这个OC对象只要实现这个JSExport协议就可以了。
1.Objective-C调用Javascript:
//Objective-C -> JavaScript
JSContext * context = [[JSContext alloc] init];
[context evaluateScript:@"function maxNum(x, y) { return x > y ? x : y}"];
JSValue * max = context[@"maxNum"];
NSLog(@"function:%@",max);
2.Javascript调用Objective-C,使用block或JSExport protocol:

使用block:

self.context = [[JSContext alloc] init];
self.context[@"maxNum"] = ^(NSInteger a, NSInteger b) {
        NSLog(@"%@",[NSNumber numberWithInteger:(a > b ? a : b)]);
};
[self.context evaluateScript:@"maxNum(11,13)"];

使用JSExport protocol:

//JSProtocolObj.h
//定义一个JSExport protocol
@protocol JSExportTest <JSExport>
//用宏转换下,将JS函数名字指定为maxNum;
JSExportAs(maxNum, - (NSInteger)maxNum:(NSInteger)x y:(NSInteger)y);
- (NSInteger)add:(NSInteger)a b:(NSInteger)b;
@property (nonatomic, assign) NSInteger sum;
@end

@interface JSProtocolObj : NSObject<JSExportTest>
@end

//JSProtocolObj.m
@implementation JSProtocolObj
@synthesize sum = _sum;
- (NSInteger)maxNum:(NSInteger)x y:(NSInteger)y {
    return x > y ? x : y;
}
//实现协议方法
- (NSInteger)add:(NSInteger)a b:(NSInteger)b {
    return a+b;
}
//重写setter方法方便打印信息,
- (void)setSum:(NSInteger)sum {
    NSLog(@"--%@", @(sum));
    _sum = sum;
}
@end

ViewController中调用:

//创建context
self.context = [[JSContext alloc] init];
//设置异常处理
self.context.exceptionHandler = ^(JSContext * context,JSValue *exception) {
      [JSContext currentContext].exception = exception;
      NSLog(@"exception:%@",exception);
};
_objModel = [JSProtocolObj new];
self.context[@"OCModel"] = self.objModel;
[self.context evaluateScript:@"OCModel.sum = OCModel.addB(2,3)"];
[self.context evaluateScript:@"OCModel.sum = OCModel.maxNum(12,18)"];
3.UIWebView中Objective-C与Javascript相互调用:
//test.html
<!DOCTYPE html>
<html lang="en">
    <meta charset="UTF-8">
    <title> my javascript </title>
</head>
    <style>
        p{
            padding-left:100px;
            color: aqua;
        }
    </style>
<body>
<h1>
    <script>
        document.write("hello world");
        document.write("<h1>这是一个标题</h1>");
        document.write("<p>这是一个段落</p>");
    </script>
</h1>

<p1 id="demo" >
    JavaScript 能改变 HTML 元素的内容。
</p1>
<button type="button" onclick="myFunction()">元素改变</button>
<button type="button" onclick="loginClick()">点击登录</button>
<button type="button" onclick="callSystemCamera()">调用系统照相机</button>
<script>
    function myFunction() {
        x=document.getElementById("demo");  // 找到元素
        x.innerHTML="Hello JavaScript!";    // 改变内容
    }
    function loginClick() {
        logIn("18200133537","ls123321");
    }
    function callSystemCamera() {
        //调用系统照相机
    }
    //
    function userInterface() {
        x=document.getElementById("demo");  // 找到元素
        x.innerHTML="oc-->js 内容改变";    // 改变内容
        //alert("what's your name?");
        //if (confirm("确定跳转")) {
            //window.open('http://www.baidu.com')
        //}
        x.innerHTML=prompt("你的名字是什么?","胡杨魂");
    }
</script>
</body>
</html>

ViewController中调用:

#import "ViewController.h"
#import <JavaScriptCore/JavaScriptCore.h>
@interface HTMLViewController ()<UIWebViewDelegate>
@property (nonatomic, strong) UIWebView * webView;
@property (nonatomic, strong) JSContext * jsContext;
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
    self.webView.delegate = self;
    [self.view addSubview:self.webView];
    NSString* path = [[NSBundle mainBundle] pathForResource:@"onecat" ofType:@"html"];
    NSURL* url = [NSURL fileURLWithPath:path];
    NSURLRequest* request = [NSURLRequest requestWithURL:url] ;
    [self.webView loadRequest:request];
}
#pragma mark -- UIWebViewDelegate
- (void)webViewDidFinishLoad:(UIWebView *)webView {
    //只要我们能拿到 UIWebView 的实例,然后就可以直接使用 KVC 的方法来获取它的 JSContext 对象
    _jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    _jsContext[@"callSystemCamera"] = ^() {
        NSLog(@"js-->oc 现在可以打开照相机");
    };
    _jsContext[@"logIn"] = ^() {
        NSLog(@"现在开始登录");
        NSArray * param = [JSContext currentArguments];
        for (JSValue * jsValue in param) {
            NSLog(@"%@",jsValue);
        }
    };
    //调用Javascript中方法 userInterface();
    [_jsContext evaluateScript:@"userInterface()"];
}

三、iOS8以后苹果推出了新框架WebKit,提供了替换UIWebView的组件WKWebView,大幅提升了性能、功能和稳定性,大大降低了加载网页时候占用的内存空间。

//test.html
<!DOCTYPE html>
<html lang="en">
    <meta charset="UTF-8">
    <title> my javascript </title>
</head>
    <style>
        p{
            padding-left:100px;
            color: aqua;
        }
    </style>
<body>
<h1>
    <script>
        document.write("hello world");
        document.write("<h1>这是一个标题</h1>");
        document.write("<p>这是一个段落</p>");
    </script>
</h1>

<p1 id="demo" >
    JavaScript 能改变 HTML 元素的内容。
</p1>
<button type="button" onclick="myFunction()">元素改变</button>
<button type="button" onclick="loginClick()">点击登录</button>
<button type="button" onclick="callSystemCamera()">调用系统照相机</button>
<script>
    function myFunction() {
        x=document.getElementById("demo");  // 找到元素
        x.innerHTML="Hello JavaScript!";    // 改变内容
    }
    function loginClick() {
        window.webkit.messageHandlers.login.postMessage(['18200133533', 'ls123321'])
    }
    function callSystemCamera() {
        //调用系统照相机
        window.webkit.messageHandlers.callCamera.postMessage(null)
    }
    //
    function userInterface() {
        x=document.getElementById("demo");  // 找到元素
        x.innerHTML="oc-->js 内容改变";    // 改变内容
        //alert("what's your name?");
        //if (confirm("确定跳转")) {
            //window.open('http://www.baidu.com')
        //}
        x.innerHTML=prompt("你的名字是什么?","胡杨魂");
    }
</script>
</body>
</html>

WKWebViewController中调用:

#import "WKWebViewController.h"
#import <WebKit/WebKit.h>
@interface WKWebViewController ()<WKScriptMessageHandler,WKUIDelegate>
@property (nonatomic, strong) WKWebView * webView;
@end

@implementation WKWebViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    //在创建WKWebView之前,需要先创建配置对象,用于做一些配置:
    WKWebViewConfiguration * config = [[WKWebViewConfiguration alloc] init];
    config.preferences.minimumFontSize = 18;
    self.webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height) configuration:config];
    [self.view addSubview:self.webView];
    NSString* path = [[NSBundle mainBundle] pathForResource:@"onecat" ofType:@"html"];
    NSURL* url = [NSURL fileURLWithPath:path];
    NSURLRequest* request = [NSURLRequest requestWithURL:url] ;
    [self.webView loadRequest:request];
    
    //而如果需要与在JS调用alert、confirm、prompt函数时,通过JS原生来处理,而不是调用JS的alert、confirm、prompt函数,那么需要设置UIDelegate,在得到响应后可以将结果反馈到JS端:
    self.webView.UIDelegate = self;
    //WKUserContentController 用于给JS注入对象的,注入对象后,JS端就可以使用:
    WKUserContentController * cv = config.userContentController;
     //注入JS对象名称,JS调用OC 添加处理脚本
    [cv addScriptMessageHandler:self name:@"login"];
    [cv addScriptMessageHandler:self name:@"callCamera"];
    //js端 window.webkit.messageHandlers.<name>.postMessage(<messageBody>)
}
#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    if ([message.name isEqualToString:@"login"]) {
        NSLog(@"js-->oc 开始登录:%@",message.body);
    }
    if ([message.name isEqualToString:@"callCamera"]) {
        NSLog(@"js-->oc 现在打开系统相机");
    }
}
#pragma mark -- WKUIDelegate
#pragma mark -- JS调用alert、confirm、prompt时,不采用JS原生提示,而是使用iOS原生来实现
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
    UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"温馨提示" message:@"js调用alert" preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction * action = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler();
    }];
    [alert addAction:action];
    [self presentViewController:alert animated:YES completion:nil];
}
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler {
    UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"温馨提示" message:@"js调用Confirm" preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction * actionDone = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(YES);
    }];
    [alert addAction:actionDone];
    UIAlertAction * actionCancel = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(NO);
    }];
    [alert addAction:actionCancel];
    [self presentViewController:alert animated:YES completion:nil];
}
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler {
    
    UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"温馨提示" message:@"js调用Prompt" preferredStyle:UIAlertControllerStyleAlert];
    [alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
        textField.textColor = [UIColor greenColor];
        textField.text = defaultText;
        textField.placeholder = @"请输入你的名字";
    }];
    UIAlertAction * actionCancel = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(alert.textFields.firstObject.text);
    }];
    [alert addAction:actionCancel];
    [self presentViewController:alert animated:YES completion:nil];
    NSLog(@"====%@",defaultText);
}
@end
四、使用第三方工具类:WebViewJavascriptBridge:略(暂未使用过)
结束
上一篇下一篇

猜你喜欢

热点阅读