DSBridge-iOS源码解析

DSBridge-iOS源码解析

Objective-C与JavaScript间的交互方式

1. 自定义scheme, 拦截http请求

     在iOS7以前, Objective-C与JavaScript间的交互采用的就是这种方式, JavaScript调用native的API时, 通过约定固定的scheme, 然后用window.location的方式触发UIWebView的shouldStartLoadWithRequest:的代理方法来达到调用native API的目的; native通过UIWebView提供的stringByEvaluatingJavaScriptFromString:方法来调用JavaScript API.

2. 通过JavaScriptCore获取JavaScript运行环境

     在iOS7时, 苹果推出了JavaScriptCore公共框架, 它提供了简单的方式实现在Objective-C与JavaScript间交互. 通过JavaScriptCore进行Objective-C与JavaScript间通讯时, 必须先用获取到UIWebView的JSContext, 即JavaScript的运行环境, 然后将交互对象注入到JSContext中, 这样Objective-C与JavaScript就能通过注入的交互对象进行通讯了.

注入Objective-C与JavaScript的交互对象的时机

1. UIWebView创建JSContext的时机

  • 当UIWebView加载页面解析到
      1. native方法调用JavaScript函数
    // addValue JavaScript函数
    // arguments 参数
    // completionHandler 回调
    [_webview callHandler:@"addValue"
                        arguments:[[NSArray alloc] initWithObjects:@1,@"hello", nil]
                completionHandler:^(NSString * value){
                    NSLog(@"%@",value);
                }];
                
    -(void)callHandler:(NSString *)methodName arguments:(NSArray *)args completionHandler:(void (^)(NSString *))completionHandler {
        if(!args) {
            args=[[NSArray alloc] init];
        }
        // 在window对象上通过函数名addValue找到h5注册的JavaScript函数, 最终通过webView的stringByEvaluatingJavaScriptFromString:方法调用
        NSString *script=[NSString stringWithFormat:@"(window._dsf.%@||window.%@).apply(window._dsf||window,%@)",methodName,methodName,[JSBUtil objToJsonString:args]];
        [self evaluateJavaScript:script completionHandler:^(NSString * value){
            if(completionHandler) completionHandler(value);
        }];
    }
    
    

    JavaScript调用native API

    原理图

    call_native.png
    • 1.H5发起调用请求
    // 获取交互对象
    var bridge = getJsBridge();
    function callSyn() {
    // 通过bridge的call方法调用native方法testSyn, 传递参数为{msg: "testSyn"}
        bridge.call("testSyn", {msg: "testSyn"});
    }
    
    // b native API 方法名
    // a 调用native API时传递给native方法的参数
    // c JavaScript的回调函数, 当异步调用native API时会传递此参数
    call: function(b, a, c) {
        'function' == typeof a && (c = a, a = {});
        if('function' == typeof c) {
            window.dscb = window.dscb || 0;
            var d = 'dscb' + window.dscb++;
            // 在window下添加对象d, 保存JavaScript回调函数c
            window[d] = c;
            // 将d放置在参数列表a中, 传递给native方法, 最终在native方法里, 通过在参数列表的json字符串中取出d, 
            // 然后在window对象中通过d获取JavaScript的回调函数c来完成回调操作
            a._dscbstub = d;
        }
        a = JSON.stringify(a || {});
        // 通过_dsbridge交互对象调用native API
        return window._dswk? prompt(window._dswk + b, a): 'function' == typeof _dsbridge? _dsbridge(b, a): _dsbridge.call(b, a)
    }
    
    
    • 2.触发交互对象block执行
    ctx[@"_dsbridge"]=^(NSString * method,NSString * args){
            return [JSBUtil call:method :args JavascriptInterfaceObject:JavascriptInterfaceObject jscontext:ctx_];
        };
    
    • 3.方法解析并调用
    +(NSString *)call:(NSString*) method :(NSString*) args  JavascriptInterfaceObject:(id) JavascriptInterfaceObject jscontext:(id) jscontext {
    // 方法及参数解析
    // methodOne为同步调用时的方法名称, 此时methodTwo为空
        NSString *methodOne = [JSBUtil methodByNameArg:1 selName:method class:[JavascriptInterfaceObject class]];
    // methodTwo为异步调用时的方法名称, 此时methodOne为空
        NSString *methodTwo = [JSBUtil methodByNameArg:2 selName:method class:[JavascriptInterfaceObject class]];
        SEL sel=NSSelectorFromString(methodOne);
        SEL selasyn=NSSelectorFromString(methodTwo);
        NSString *error=[NSString stringWithFormat:@"Error! \n Method %@ is not invoked, since there is not a implementation for it",method];
        NSString *result=@"";
        // 检查调用对象是否存在
        if(!JavascriptInterfaceObject) {
            NSLog(@"Js bridge method called, but there is not a JavascriptInterfaceObject, please set JavascriptInterfaceObject first!");
        } else {
        // json为发起调用时的参数列表
            NSDictionary * json=[JSBUtil jsonStringToObject:args];
        // cb 保存JavaScript的回调函数名称, 当发起异步调用时为有值
            NSString * cb;
            do{
                if(json && (cb= [json valueForKey:@"_dscbstub"])) {
                // 异步调用
                    if([JavascriptInterfaceObject respondsToSelector:selasyn]) {
                        void (^completionHandler)(NSString *,BOOL) = ^(NSString * value,BOOL complete) {
                            value=[value stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
                            NSString *del=@"";
                            // 发起连续回调时, complete为YES, 否则为NO
                            if (complete) {
                            // 删除保存在window对象中的cb对象, 这个对象是H5发起调用时保存到window中的
                               del=[@"delete window." stringByAppendingString:cb];
                            }
                            // 在window对象上通过cb调用JavaScript函数
                            NSString*js=[NSString stringWithFormat:@"try {%@(decodeURIComponent(\"%@\"));%@; } catch(e){};",cb,(value == nil) ? @"" : value,del];
                            if([jscontext isKindOfClass:JSContext.class]) {
                                [jscontext evaluateScript:js ];
                            } else if([jscontext isKindOfClass:WKWebView.class]) {
                                @synchronized(jscontext) {
                                    UInt64  t=[[NSDate date] timeIntervalSince1970]*1000;
                                    g_ds_js_cache=[g_ds_js_cache stringByAppendingString:js];
                                    if(t-g_ds_last_call_time<50) {
                                        if(!g_ds_have_pending){
                                            [self evalJavascript:(WKWebView *)jscontext :50];
                                            g_ds_have_pending=true;
                                        }
                                    } else {
                                        [self evalJavascript:(WKWebView *)jscontext  :0];
                                    }
                                }
                            }
                        };
                        SuppressPerformSelectorLeakWarning(
                                                           [JavascriptInterfaceObject performSelector:selasyn withObject:json withObject:completionHandler];
                                                           );
                        //when performSelector is performing a selector that return value type is void,
                        //the return value of performSelector always seem to be the first argument of the selector in real device(simulator is nil).
                        //So,you should declare the return type of all api as NSString explicitly.
                        if (result==(id)json) {
                            result=@"";
                        }
                        break;
                    }
                } else if ([JavascriptInterfaceObject respondsToSelector:sel]) {
                // 同步调用
                    SuppressPerformSelectorLeakWarning(
                                                       result=[JavascriptInterfaceObject performSelector:sel withObject:json];
                                                       );
                    break;
                }
                NSString*js=[error stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
                js=[NSString stringWithFormat:@"window.alert(decodeURIComponent(\"%@\"));",js];
                if ([jscontext isKindOfClass:JSContext.class]) {
                    [jscontext evaluateScript:js ];
                } else if ([jscontext isKindOfClass:WKWebView.class]) {
                    [(WKWebView *)jscontext evaluateJavaScript :js completionHandler:nil];
                }
                NSLog(@"%@",error);
            }while (0);
        }
        if (result == nil||![result isKindOfClass:[NSString class]]) {
            result=@"";
        }
        return result;
    }
    

你可能感兴趣的:(DSBridge-iOS源码解析)