美文网首页
OC与H5交互

OC与H5交互

作者: Aliyunyun | 来源:发表于2017-08-26 09:55 被阅读199次

    OC与H5交互

    一、 方案

    I. iOS6的方案

    ios6 主要是通过UIWebView来实现交互,可以通过一个第三方库来实现(EasyJSWebView的方法来注入JS代码实现交互)

    1. JS 调用 OC

      • JS端: 通过iframe来实现document.documentElement.appendChild(iframe);
      • APP端:主要是通过UIWebView,实现UIWebView的代理方法,截取Request,来实现JS调用OC的代码,
    2. OC调用JS

    - (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;

    II. iOS7的方案

    ios7 主要是通过UIWebView来加载h5的页面,但是IOS7增加了JavaScriptCore来实现原生和H5的交互

    1. JS 调用OC

      • JS端: 由于OC可以通过JavaScriptCore JSExport向JS端注入OC的对象,所以JS端可以直接调用OC的方法。OCObject.test("parmes")
      • APP端:由于可以通过JSContxt直接注入对象,所以可以直接访问对象的方法
    2. OC 调用JS

    - (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;

    III. iOS8之后的方案

    iOS8之后推荐使用WKWebView,WKWebView 不支持JavaScriptCore, 但是提供了 userContentController WKUserContentController 来实现JS的注入, 主要的方式可以叫做messageHandler

    1. JS 调用 OC

      • JS 端:postMessage window.webkit.messageHandlers.Tips.postMessage('xiao黄');

      • APP端:主要是通过WKWebView的代理,来截取方法和参数, WKScriptMessageHandler

    2. OC调用JS

      - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;
      
      
      

    二、UIWebView原生方法

    I. 设置UIWebView的代理,截取request

    • JS调用OC的方法

    JS代码

    //这个方法刷新的时候不会导致页面消失
    function loadURL(url) {
      var iFrame;
      iFrame = document.createElement("iframe");
      iFrame.setAttribute("src",url);
      iFrame.setAttribute("style","display:none");
      iFrame.setAttribute("height","0px");
      iFrame.setAttribute("weight","0px");
      iFrame.setAttribute("frameborder","0");
      document.body.appendChild(iFrame);
    
      iFrame.parentNode.removeChild(iFrame);
      iFrame = null;
    }
    
    // JQuery 的写法,$() 就是JS的document.getElementById()
    $(document).ready(function () {
      console.log("xxxxxxxxx");
      $("#login_btn").click(function () {
        // loadURL 来调用OC的方法
        loadURL("yyHetRequest://scan");
      });
    });
    
    

    OC代码

    
    - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
    {
        
        NSLog(@"%@ type ", request.URL.absoluteString);
        NSURL *url = [request URL];
        NSString *scheme = [url scheme];
        if ([scheme isEqualToString:@"yyhetrequest"]) {
            [self handleJSRequest:url];
        }
        return true;
    }
    
    
    - (void)handleJSRequest:(NSURL *)url
    {
        NSString *host = [url host];
        if ([host isEqualToString:@"scan"]) {
            [self scanAction];
        }
        
    }
    
    • OC 调用JS的代码
     NSString *jsCode = 
                        @"var demoP =  document.getElementById('demo');"
                        @"demoP.innerHTML = 'ios write'";
     [_webView stringByEvaluatingJavaScriptFromString:jsCode];
    

    三、EasyJSWebView的使用和原理

    I. EasyJsWebView的库,使用方法

    1、APP端

    // 创建接口对象
    @interface MyJSInterface : NSObject
    
    - (void) test;
    - (void) config: (NSString*) param;
    - (void) testWithTwoParam: (NSString*) param AndParam2: (NSString*) param2;
    
    - (NSString*) testWithRet;
    
    @end
    
    
    // 在OC中向JS 注入OC对象
    MyJSInterface* interface = [MyJSInterface new];
    [self.myWebView addJavascriptInterfaces:interface WithName:@"bindJavaScript"];
    [interface release];
    

    2、H5端

    //js中调用OC的方法
    var appInterfaceNS = 'bindJavaScript'; // app注入接口的命名空间
    var __AppInterface = window[appInterfaceNS] || {}; // 接入APP接口
    
    /**
     * 通知app开始初始化
     * @param  {json}     options 配置信息及提交给app的初始化信息
     * @return {Function}         返回值由app决定,该值为非必须值
     */
    $this.config = function(options){
        var data = {};
        data = JSON.stringify(options);
        return typeof __AppInterface.config === 'function' && __AppInterface.config(data);
    };
    
    

    II. EasyJsWebView的原理

    EasyJS的执行过程主要是两个阶段:

    • 注入阶段
    • 代码执行解析阶段

    1、注入阶段

    注入的代码分为两个部分:

    1. 注入一段JS代码,创建EasyJs对象。

    2. 将OC方法,在JS里面再创建一次,并且保存到JS的一个数组。

    注入OC对象的方法

    [self.myWebView addJavascriptInterfaces:interface WithName:@"MyJSTest"];
    
    

    注入源码解析:

    EasyJSWebView 包含的函数:

    • EasyJSWebView
    • EasyJSDataFunction
    • EasyJSWebViewProxyDelegate

    a、 EasyJSWebView的头文件,包含了注入JS的过程和加入OC对象的过程

    @interface EasyJSWebView : UIWebView
    
    // All the events will pass through this proxy delegate first
    @property (nonatomic, retain) EasyJSWebViewProxyDelegate* proxyDelegate;
    
    - (void) initEasyJS;
    - (void) addJavascriptInterfaces:(NSObject*) interface WithName:(NSString*) name;
    
    @end
    
    
    

    b、 EasyJSWebViewProxyDelegate 主要来实现UIWebView的代理方法,

    EasyJSWebViewProxyDelegate的JS代码, 这段JS代码主要是向H5的JS环境中创建一个叫EasyJS的对象,总共包含4个对象:

    • __callbacks : 存储回调的数组。
    • invokeCallback: 执行callback的函数。
    • call: JS调用OC的方法的函数,本质上是组装UIWebView的request,格式:easy-js:MyJSTest:s%12%cd%12s__cd1234562,iOS会在代理里面去解析这个字符串。
    • inject: OC想JS注入接口,方便H5端来调用OC的接口。
    
    window.EasyJS = 
    {
    __callbacks: {},
    //回调数组
    invokeCallback: 
        function (cbID, removeAfterExecute)
            {
                var args = Array.prototype.slice.call(arguments);
                args.shift();
                args.shift();
                for (var i = 0, l = args.length; i < l; i++)
                {
                    args[i] = decodeURIComponent(args[i]);
                }
                var cb = EasyJS.__callbacks[cbID];
                if (removeAfterExecute)
                    {
                        EasyJS.__callbacks[cbID] = undefined;
                    }
                return cb.apply(null, args);
            },
    //根据OC的函数名来创建JS函数,通过iframe调用OC的UIWevView的delegate
    //"- (BOOL)webView: shouldStartLoadWithRequest: navigationType:"
    // 规则:ios端拿到字符串后`easy-js:MyJSTest:s%12%cd%12f__cd1234562`
    // easy-js 表示js调用OC方法的开头
    // MyJSTest 表示OC想JS暴露的对象
    // s 表示数组下一位为参数
    // f 表示数组下一位为函数,主要f后面跟着的内容是一个key,这个key表示OC对JS的回调,JS会将这项回调保存到
    call: 
        function (obj, functionName, args){
            var formattedArgs = [];
            for (var i = 0, l = args.length; i < l; i++)
            {
                if (typeof args[i] == "function")
                {
                    formattedArgs.push("f");
                    var cbID = "__cb" + (+new Date);
                    EasyJS.__callbacks[cbID] = args[i];formattedArgs.push(cbID);
                }else{
                    formattedArgs.push("s");
                    formattedArgs.push(encodeURIComponent(args[i]));
                }
            }
            var argStr = (formattedArgs.length > 0 ? ":" + encodeURIComponent(formattedArgs.join(":")) : "");
            var iframe = document.createElement("IFRAME");
            iframe.setAttribute("src", "easy-js:" + obj + ":" + encodeURIComponent(functionName) + argStr);
            document.documentElement.appendChild(iframe);
            iframe.parentNode.removeChild(iframe);
            iframe = null;
            var ret = EasyJS.retValue;EasyJS.retValue = undefined;
            if (ret){
                return decodeURIComponent(ret);
            }
        },
    //讲OC的方法,注入到JS代码中,实现JS回调OC的方法
    inject: 
        function (obj, methods){
            window[obj] = {};
            var jsObj = window[obj];
            for (var i = 0, l = methods.length; i < l; i++)
            {
                (
                function (){
                    var method = methods[i];
                    var jsMethod = method.replace(new RegExp(":", "g"), "");
                    jsObj[jsMethod] = function ()
                        {
                        return EasyJS.call(obj, method, Array.prototype.slice.call(arguments));
                        };
                    }
                )();
            }
        }
    
    };
    
    

    EasyJSWebViewProxyDelegate实现UIWebView的代理方法

    下面有一段通过函数名来调用一个对象的函数的涉及到两个类:NSMethodSignature `` NSInvocation

    
    - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
        
        NSString *requestString = [[request URL] absoluteString];
        if ([requestString hasPrefix:@"easy-js:"]) {
            /*
             A sample URL structure:
             easy-js:MyJSTest:test
             easy-js:MyJSTest:testWithParam%3A:haha
             */
            NSArray *components = [requestString componentsSeparatedByString:@":"];
            //NSLog(@"req: %@", requestString);
            
            NSString* obj = (NSString*)[components objectAtIndex:1];
            NSString* method = [(NSString*)[components objectAtIndex:2]
                                stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
            
            NSObject* interface = [javascriptInterfaces objectForKey:obj];
            
            // execute the interfacing method
            SEL selector = NSSelectorFromString(method);
            // 获取方法签名
            NSMethodSignature* sig = [[interface class] instanceMethodSignatureForSelector:selector];
            // 获取invoker对象
            NSInvocation* invoker = [NSInvocation invocationWithMethodSignature:sig];
            invoker.selector = selector;
            invoker.target = interface;
            
            NSMutableArray* args = [[NSMutableArray alloc] init];
            
            // 获取参数
            if ([components count] > 3){
                NSString *argsAsString = [(NSString*)[components objectAtIndex:3]
                                          stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
                
                NSArray* formattedArgs = [argsAsString componentsSeparatedByString:@":"];
                for (int i = 0, j = 0, l = [formattedArgs count]; i < l; i+=2, j++){
                    NSString* type = ((NSString*) [formattedArgs objectAtIndex:i]);
                    NSString* argStr = ((NSString*) [formattedArgs objectAtIndex:i + 1]);
                    
                    if ([@"f" isEqualToString:type]){
                        EasyJSDataFunction* func = [[EasyJSDataFunction alloc] initWithWebView:(EasyJSWebView *)webView];
                        func.funcID = argStr;
                        [args addObject:func];
                        [invoker setArgument:&func atIndex:(j + 2)];
                    }else if ([@"s" isEqualToString:type]){
                    //invoker 传入参数
                        NSString* arg = [argStr stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
                        [args addObject:arg];
                        [invoker setArgument:&arg atIndex:(j + 2)];
                    }
                }
            }
            //调用函数
            [invoker invoke];
            
            //return the value by using javascript
            if ([sig methodReturnLength] > 0){
                NSString* retValue;
                [invoker getReturnValue:&retValue];
                
                if (retValue == NULL || retValue == nil){
                    [webView stringByEvaluatingJavaScriptFromString:@"EasyJS.retValue=null;"];
                }else{
                    retValue = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL,(CFStringRef) retValue, NULL, (CFStringRef)@"!*'();:@&=+$,/?%#[]", kCFStringEncodingUTF8));
                    [webView stringByEvaluatingJavaScriptFromString:[@"" stringByAppendingFormat:@"EasyJS.retValue=\"%@\";", retValue]];
                }
            }
            
        
            
            return NO;
        }
        
        if (! self.realDelegate){
            return YES;
        }
        
        return [self.realDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
    }
    
    
    

    一、OC 调用 JS

      NSString *jsCode = 
                        @"var demoP =  document.getElementById('demo');"
                        @"demoP.innerHTML = 'ios write'";
      [_webView stringByEvaluatingJavaScriptFromString:jsCode];
    

    二、JS 调用 OC

    • 两个方案:
      1. 截取request
      2. 用JavaScriptCore

    2.使用JavascriptCore

    OC代码

    JSTextObjext

        //JSTestObjext.h
        
        @protocol JSTextObjectProtocol <JSExport>
        
        - (void)callOCFunction;
        - (void)callOCFunctionWithFirstParments:(NSString *)parmentOne;
        - (void)callOCFunctionWithFirstParments:(NSString *)parmentone SecondParments:(NSString *)parmentsTwo;
        
        @end
        
        @interface JSTestObjext : NSObject <JSTextObjectProtocol>
        
        @end
        
        //JSTestObjext.m
        @implementation JSTestObjext
    
        - (void)callOCFunction
        {
            NSLog(@"callOCFunction");
        }
        - (void)callOCFunctionWithFirstParments:(NSString *)parmentOne
        {
            NSLog(@"callOCFunctionWithFirstParments %@",parmentOne);
        }
        - (void)callOCFunctionWithFirstParments:(NSString *)parmentone SecondParments:(NSString *)parmentsTwo
        {
             NSLog(@"callOCFunctionWithFirstParments one %@  two %@",parmentone, parmentsTwo );
        }
        @end
    
    

    在webview 加载完后创建想context 注册

        #pragma UIWebViewDelegate
    -(void)webViewDidFinishLoad:(UIWebView *)webView
    {
        JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
        JSTestObjext *obj =  [JSTestObjext new];
        
        // JS调用OC的对象方法
        context[@"testObj"] = obj;
        
        //JS调用OC的Block方法
        context[@"log"] = ^(){
            NSLog(@"log log log");
            NSArray *argument = [JSContext currentArguments];
            if (argument.count < 3) {
                return ;
            }
            
            NSString *onePar = [argument[0] toString];
            NSString *twoPar = [argument[1] toString];
            NSString *thrPar = [argument[2] toString];
            NSLog(@" %@ %@ %@", onePar, twoPar, thrPar);
            
            NSString *shareSuccess = [NSString stringWithFormat:@"showAlert(%@)",onePar];
            [[JSContext currentContext]evaluateScript:shareSuccess];
        };
    }
    
    

    JS调用

    // JQuery 的写法,$() 就是JS的document.getElementById()
    $(document).ready(function () {
      console.log("xxxxxxxxx");
      $("#get_cookie").click(function () {
      
        // 调用想context注册的方法
        testObj.callOCFunction();
     
        
      });
    });
    

    JS与OC交互方案

    相关文章

      网友评论

          本文标题:OC与H5交互

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