iOS Hybrid开发演进

Web技术相对于Native来说有很多优势,比如:跨端(浏览器、Android、iOS)、排版更灵活、实时生效等。所以,在开发中我们经常会采用一些Web页面嵌入到APP中。

这样,就引入了web与Native的交互,往往也就是JavaScript与Native的交互。

JS与Native的交互,可以大致分为:Native调用JS、JS调用Native。

Native调用JS

Native调用JS比较简单

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

//WKWebView
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;
复制代码

注意:

  • UIWebView执行JS是同步的,在WKWebView中是异步的。
  • UIWebView返回值只支持可String化的基本类型,并不支持集合类型(Array、Dictionary)和对象。
  • WKWebView执行JS的回调Block中支持集合类型和对象,也支持可String化的基本类型。
h5代码
<!DOCTYPE html>
<html>
<head>
	<title>WKWebView</title>
    <meta charset="UTF-8" />
	
    <script type="text/javascript">
        function methodA(username, password) {
            console.log("Hello, I am methodA");
            let dict = {
                "username" : username,
                "password" : password
            };
            return "username: " + username + ", password: " + password;
            <!--return dict;-->
            <!--return ["A", "B"];-->
        }
	</script>
</head>
<body>
</body>
</html>
复制代码
Native代码
//UIWebView
NSString *str = [self.webView stringByEvaluatingJavaScriptFromString:@"methodA('zhangsan', '123456')"];
NSLog(@"str");

//WKWebView
[self.webView evaluateJavaScript:@"methodA('zhangsan', '123456')" completionHandler:^(id _Nullable value, NSError * _Nullable error) {
    if (error) {
        NSLog(@"%@", error);
        return ;
    }
    NSLog(@"%@", value);
}];
复制代码

JS调用Native

其实,JS调用Native的交互经历过几个阶段。

iOS7之前

iOS7之前,JS想要调用Native时主动触发加载一个新的url。我们通过拦截URL通过提前约定的协议来判断是否为调用原生功能,用这种方式来处理JS对Native的调用。

JS端主要通过window.location.assign来触发新的加载。

iOS中在- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType中针对URL做判断。

JS代码
<!DOCTYPE html>
<html>
<head>
	<title>WKWebView</title>
	<meta charset="UTF-8" />
	<script type="text/javascript">
        function callNative() {
            window.location.assign("native://func1?name=zhangsan&password=123456");
        }
	</script>
</head>
<body>
	<button onClick="callNative()">CallNative</button>
</body>
</html>
复制代码
OC代码
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    NSString *urlPath = [request.URL absoluteString];
    /*
     * 与Web端协商定义交互协议 scheme://function_name?para1=value1&para2=value2&para3=value3&callback=callback_value
     *  scheme为native时,是要与原生进行交互。
     *  function_name指定要调用原生的什么功能。
     *  para1是参数名称,value1是参数值
     *  callback是原生执行完后对js的回调
     */
    if ([urlPath hasPrefix:@"native://"]) {
        urlPath = [urlPath substringFromIndex:@"native://".length];
        NSArray *questionMarkArray = [urlPath componentsSeparatedByString:@"?"];
        NSString *funcName;
        NSArray *paraArray;
        if (questionMarkArray.count > 0) {
            funcName = [questionMarkArray firstObject];
            if (questionMarkArray.count > 1) {
                // 从问号后还是都认定为参数
                NSString *paraStr =  [urlPath substringFromIndex:funcName.length + 1];
                paraArray = [paraStr componentsSeparatedByString:@"&"];
            }
        }
        
        // funcname 匹配
        if ([funcName isEqualToString:@"func1"]) {
            
        } else if ([funcName isEqualToString:@"func2"]) {
            
        } else if ([funcName isEqualToString:@"func3"]) {
            
        } else {
            
        }
        
        return NO;
    } else {
        return YES;
    }
}
复制代码

iOS7

iOS7开始,系统公开了JavaScriptCore框架,我们可以基于此来进行JS与Natvie的交互。

插播Javascript的调试方法(我们可以在Mac上调试JS代码)。

Safari的偏好设置中勾选底部的在菜单栏中显示“开发”菜单,然后Safari的菜单栏中就多出了开发菜单,当webview加载完h5后就可以选择对应的模拟器下对应的应用进行web调试。详细步骤和真机的调试方法请自行Google。

iOS7中通常采用的方式是拿到JSContext,然后将OC的方法或对象注入到JS中。

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    self.jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    
    // JS的异常回调
    self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exception) {
        NSLog(@"%@", exception);
    };
    
    // 注入方法
    self.jsContext[@"addNum"] = ^(int a, int b) {
        return a + b;
    };
    
    // 注入对象
    NSDictionary *dict = @{@"aaa" : @"aaa", @"bbb" : @"bbb"};
    self.jsContext[@"dict"] = [JSValue valueWithObject:dict inContext:self.jsContext];
}
复制代码

然后在JS中就可以调用相应的对象和方法,调用方式和调用原生的方法或对象一致。

function callByContext() {
    <!-- addNum为原生向JS注入的方法 -->
    var num = addNum(5, 8);
    console.log(num);
    <!-- dict为原生向JS注入的对象 -->
    console.log(dict);
}
复制代码

iOS8 WKWebView

UIWebView有很多性能问题,所以苹果在iOS8中引入了新的浏览器组件WKWebView。

并且在iOS12后苹果废弃了UIWebView,所以请同学们务必赶紧升级到WKWebView!!!

在WKWebView的交互中,要使用WKUserContentController。在WKWebView构建的时候,需要传入WKWebViewConfiguration,而WKWebViewConfiguration可以添加WKUserContentController为ScriptMessageHandler。

WKUserContentController *userContent = [[WKUserContentController alloc] init];
//伪代码
[userContent addScriptMessageHandler:id<WKScriptMessageHandler> name:@"MyNative"];
    
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
config.userContentController = userContent;
    
WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:config];
复制代码

handler 对象需要实现WKScriptMessageHandler协议,当 JS 端通过 window.webkit.messageHandlers 发送 Native 消息时,handler 对象的协议方法被调用,通过协议方法的相关参数传值。

#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {}
复制代码

JS调用iOS的方法

// Javascript
function callNative() {
  window.webkit.messageHandlers.MyNative.postMessage('body');
}
复制代码

相关的JS和iOS代码已上传至GitHub(github.com/NewFarmer21…),可自行下载和调试。

转载于:https://juejin.im/post/5d06fca4518825498b57dff0

猜你喜欢

转载自blog.csdn.net/weixin_33795833/article/details/93177478
今日推荐