美文网首页
iOS Objective-C与Javascript交互总结

iOS Objective-C与Javascript交互总结

作者: 倾世圣伊 | 来源:发表于2017-01-09 16:53 被阅读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:略(暂未使用过)
    结束

    相关文章

      网友评论

          本文标题:iOS Objective-C与Javascript交互总结

          本文链接:https://www.haomeiwen.com/subject/htxhbttx.html