现在纯原生的APP是越来越少了,更多的都加入了Web混合开发(有UiWebView和iOS 8之后的WKWebView,后者在性能上优化了许多),甚至有些直接是WebAPP。JSPatch、weex以及ReactNative等热更新技术也相当流行,虽然2017年3月份苹果的一封邮件警告,让许多使用者对这些热更新技术有点害怕。
既然用到了web,就避免不了JS和OC语言的互相调用。
本文介绍自己项目中的js-oc互相调用实现方法。从一开始简单的url拦截到最近的基于JavaScriptcore建立的JSbrige两套方案。
URL拦截方式
JS调用OC:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSString * currentUrl = webView.request.URL.absoluteString;
//判断是否是单击
if (navigationType == UIWebViewNavigationTypeLinkClicked)
{
NSURL *url = [request URL];
NSLog(@"当前url%@",url);
NSString *urlStr = url.absoluteString;
if ([[url scheme] isEqualToString:@"cloudsc6"]){
if ([[url host] isEqualToString:@"PLAYVOICE"]) {
NSLog(@"准备播放语音消息");
}
}
else {
return YES;
}
}
return YES;
}
这个实现方式事实上有很多局限,首先参数的传递就不太友好,参数放在url中实现实在不是很优雅的实现方式。
而且,工作中实测,当JS实现事件的方式是Ajax异步的时候,有些事件这个代理是拦截不到的。
当时解决的方案是使用了NSURLProtocol全局去拦截url请求。如下
@implementation InnerResourcesURLProtocol
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
BOOL can = NO;
//NSLog(@"--->can handled Request? #%lu: URL = %@", (unsigned long)requestCount++, request);
// NSLog(@"%@",request);
if([NSURLProtocol propertyForKey:CLOUDSEE_INNER_PROTOCOL_REQ_HANDLED_KEY inRequest:request]){
can = NO;
}else {
NSURL *url = [request URL];
if(([[url scheme] isEqualToString:DEFAULT_SCHEMA])||([[url scheme]isEqualToString:@"th"])){
can = YES;
}
}
return can;
}
- (void) startLoading
{
AppDelegate *dd = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSURL *url = [self.request URL];
if ([[url scheme] isEqualToString:@"PLAYVOICE"]) {
}
}
同样地也有其局限性。
OC调用JS
-(void)webViewDidFinishLoad:(UIWebView *)webView
{
NSString *scriptResult = [self.webView stringByEvaluatingJavaScriptFromString:@"didWebViewScroll()"];
if([scriptResult isEqualToString:@"NO"]) {
self.webView.scrollView.bounces=NO;
}
}
这个实现方式简单,但是同样也相当不友好。首先传参数在didWebViewScroll()中的()实现,不仅对长度有限,而且如果参数有含有''会导致截取错误代码。
而且这个方案下,有些事件安卓能截取,iOS 不能截取。有些事件iOS 能截取,安卓不能截取。跨平台性也不好。
WebViewJavascriptBridge
网上有一个著名的库,星星数已达8823。但是其底层实现原理还是基于URL拦截方式去实现。
再次证明星星数不能代表什么,只能说其比较出名。
JavaScriptcore桥接实现
如果你的APP不需要支持iOS 7之前的版本,可以使用以下方案,我们公司现在用的就是这个方案。
苹果在iOS 7中加入了JavaScriptCore框架。该框架让Objective-C和JavaScript代码直接的交互变得更加的简单方便。
桥接关键 和JS调用OC
-(void)webViewDidFinishLoad:(UIWebView *)webView{
self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
//打印JS异常
[self.jsContext setExceptionHandler:^(JSContext *context, JSValue *value) {
NSLog(@"oc catches the exception: %@", value);
}];
//以下是桥接JS与OC的关键
JSModel * model = [[JSModel alloc]init];
model.controller = self;
model.JSContext = self.jsContext;
//以下是H5中调用的方法名称
NSString * key = @"DDJSBridge";
[self.jsContext setObject:model forKeyedSubscript:key];
JSValue * loadData = self.jsContext[@"loadData"];
//以下是回调JS方法,可以传入封装好的参数
[loadData callWithArguments:@[self.jsonStr]];
if (isLoadData) {
[self firstIn];
}
isLoadData = NO;
// 以下是执行JS方法
// [self.jsContext evaluateScript:self.htmlCont];
}
OC调用JS
只要将JSModel继承JSExport,再本地实现以下方法
-(void)call:(NSString *)methodName :(NSDictionary *)params :(JSValue *)callBack{
NSDictionary * data = [params objectForKey:@"data"];
if ([methodName isEqualToString:@"showShareButton"]) {
NSString * iconsStrUrl = [data objectForKey:@"icon"];
if (iconsStrUrl) {
NSURL * iconUrl = [NSURL URLWithString:iconsStrUrl];
NSData * imageData = [NSData dataWithContentsOfURL:iconUrl];
UIImage *image = [UIImage imageWithData:imageData scale:3];
UIBarButtonItem *rightBarButton = [[UIBarButtonItem alloc]
initWithImage:image
style:UIBarButtonItemStylePlain
target:self
action:@selector(rightClick)];
self.controller.navigationItem.rightBarButtonItem = rightBarButton;
}
}
至于跨平台性,也比第一个方案要友好,安卓也有类似的实现方案。
第二个方案的DEMO已经上传,可以下载
JSBrigeOC版本
JSBrigeSwift版本
JavascriptBridge实现内部原理
有一个百度团队的博客写的很好,可以参考。
深入浅出 JavaScriptCore
浅谈JavaScriptCore
阿里无限团队接口实现规范
我们也是基本按照它的接口规范的
阿里无限团队接口实现规范