标签:
现在越来越多的APP都是H5和原生混合开发,这样确实方便快捷,但是H5的部分总避免不了很多与原生的交互,原生调JS函数还比较简单,原生的API函数stringByEvaluatingJavaScriptFromString就可以完成需求,但是JS调原生的函数,系统没有提供API,所以很多人公司都采用标记位的形式完成,我们公司的也不例外,项目开发了很久,原生和JS交互一直是我负责,我们项目这块也要大量的交互操作,随便版本迭代,问题也越来越多,然后就想了很多办法去解决这个问题。
一,我原本的方案,拦截URL完成
拦截URL的方式就是在如下方法中拦截抛出的URL,URL中包含关键的标识字段,根据这些字段调用原生的函数,这样H5中的相应操作全部可以抛出包含关键字段的URL,然后原生根据各自的字段采取不同的操作,完成H5和原生的交互.
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
返回YES的话Webview继续加载新的URL,返回NO就不加载,这样的话凡是需要采取操作的URL都可以返回NO,让webview不再加载,只调用原生的函数就行。
但是项目中H5的页面实在太多,很多公共的接口都是固定的,例如弹出用户登录,播放视频,打开图片游览器,还有跳转商品,活动,新闻,通知详情,所以就需要封装出一个框架,让所有的公共接口都可以相应公共接口的JS和原生互调,我这里采取的是用分类的方式,在这里采用分类的方式,设置代理,也有一些技巧:
1,在分类中定义一个代理,并用运行时实现setter和getter,这里设定代理的协议是UIWebViewDelegate的好处就是外面感觉不到代理转换了,还以为是直接的设置webview的代理,对外面暴露的越少,使用越简单。
@property(nonatomic,weak) IBInspectable id<UIWebViewDelegate>customDelegate;
-(id<UIWebViewDelegate>)customDelegate
{
return objc_getAssociatedObject(self, @selector(customDelegate));
}
-(void)setCustomDelegate:(id<UIWebViewDelegate>)customDelegate
{
objc_setAssociatedObject(self, @selector(customDelegate), customDelegate, OBJC_ASSOCIATION_ASSIGN);
}
2,设置自己的delegate是自己,并且实现代理方法,
self.delegate = self;
3,关键的地方来了,先上代码吧:
//让每个webView有依然可以设置代理并且相应各自的方法
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
//H5抛出的URL
NSString *absoluteString = request.URL.absoluteString;
BOOL action = [self takeActionWith:absoluteString]; // 根据抛出的URL采取不同的操作
if (!action) {
return NO;
}
//执行每个webView自己的customDelegate代理方法
if (self.customDelegate && [self.customDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {
return [self.customDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
}
return YES;
}
拦截到的URL后根据标识字段采取各自的操作,并且判断是否需要继续加载当前URL,然后调用代理的加载方法,除了公共的接口外,还可以让外面的每个地方都自定义自己需要的操作
4,完工,给用户提供接口
-(void)setUrlString:(NSString *)urlString block:(void(^)(NSArray *arr))postImage delegate:(id<UIWebViewDelegate>)delegate;
然后在这个方法里面可以加载URL,然后设置代理,调用这个分类的地方,如果需要自定义UIWebViewDelegate的代理方法 ,也可以设置delegate,然后实现自己需要单独完成的操作,对外使用webview没有丝毫变化,加载url,设置代理,实现代理方法。
至此,封装出一个公共接口让JS和原生互调的分类就算完成了,在最开始我也是这么做的。但是随便版本迭代,交互越来越多,关键字段越来越多,通过字符串比对的方法,很容易跟其他的URL冲突,然后得到不想要的效果,而且,字符串比对还有先后顺序,如果一个URL包含两个关键字段,那么哪个字符串比对写在前面,就先执行哪个标识字段对应的函数。
显然,这是不合理的,也不是我们能容忍的
二,规定抛出URL的字段的规范
于是,我就想如何去解决这个问题,不过思前想后,还是觉得URL抛出没有任何规范,仅仅只是判断URL中是否包含某些字段的做法,其他地方的字段H5那边随便配备,显然是非常不合理的,于是,上网看了很多方案后,和后台约束的URL抛出字段的规范:
func://name=leehonn/age=24
这样的字段简洁明了,func是函数名,后面的都是参数,我们可以通过OC提供的API方法,先通过"//"将URL分割成一个数组,数组的第一个元素就是函数名,再把后面的字符串通过"/"分割,取得各自的参数,这样做的好处就是没有多余的字段,不容易冲突,需要的字段也都在,解析起来也方便。
当取得函数名,就可以执行函数了:
SEL originalSelector = NSSelectorFromString(absoluteString);
[self performSelector:originalSelector];
至此大功告成,优化完毕,而且这块和安卓可以通用,安卓那边也可以通过反射去执行函数,但是据说那样做性能会降低,但是即使不用反射,就用原来的字符串比对,也比之前URL的格式规范不少,不过对于iOS这边,确实是比之前有优化的。
三,采取第三方框架
后来还是感觉这种形式很蹩脚,就将眼光方向第三方框架,然后就找到了WebViewJavascriptBridge,星星数也挺多,然后就拿来用,另外,这个框架也是采取拦截URL的方式,只是他将细节封装起来了而已,而且这个框架也需要JS代码那边的配合,并且,这个框架也不像大家想的那样能够拦截JS的onClick事件,具体使用方法大家可以去github上看官方介绍,不过别忘了,这个框架是需要JS那边的代码配合的,需要JS为iOS专门适配一套,安卓那边也有自己的一套方案,如果你们做H5的同事不嫌麻烦,可以弄两套
还有据说可以通过stringByEvaluatingJavaScriptFromString函数就可以完成JS和原生的互调,有知道的大神希望分享下
标签:
原文地址:http://www.cnblogs.com/leehonn/p/5644646.html