最近在iOS项目中需要使用到oc与js之间的相互调用,而且要求是实现方式必须与Android中的相同,方便js中统一处理。于是在对第三方库WebViewJavascriptBridge进行研究之后,仿照Android中的WebView与JS的交互机制,实现了一个,在这里分享给大家。
首先要说明的是,在iOS中js调用Objective-C的代码只能通过重定向的形式进行,即js中通过修改iframe的src,或者直接跳转到一个url,在Objective-C中通过UIWebView的
webView:shouldStartLoadWithRequest:navigationType:方法拦截这个跳转,然后通过解析跳转的url获取js需要调用的方法名和参数。而在Android中,只需要调用WebView的addJavascriptInterface方法,将一个js对象绑定到一个java类,在类中实现相应的函数,当js需要调用java的方法时,只需要直接在js中通过绑定的对象调用相应的函数即可。
显然Android中js交互的方式要比iOS上方便得多,因此,我们可以在iOS上实现一套与Android相类似的机制。下面先说明一下实现的原理,要在js中直接通过绑定的对象调用相应的函数,那么就需要在js中添加相应的代码,但是为了确保与Android的一致性,js代码应该在客户端以注入的形式加入。所以,我们先实现一下需要注入的代码:
01.
;(function() {
02.
var messagingIframe,
03.
bridge =
'external'
,
04.
CUSTOM_PROTOCOL_SCHEME =
'jscall'
;
05.
06.
if
(window[bridge]) {
return
}
07.
08.
function _createQueueReadyIframe(doc) {
09.
messagingIframe = doc.createElement(
'iframe'
);
10.
messagingIframe.style.display =
'none'
;
11.
doc.documentElement.appendChild(messagingIframe);
12.
}
13.
window[bridge] = {};
14.
var methods = [
"test1"
,
"test2"
];
15.
for
(var i=
0
;i<methods.length;i++){
16.
var method = methods[i];
17.
var code =
"(window[bridge])[method] = function "
+ method +
"() {messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + ':' + arguments.callee.name + ':' + encodeURIComponent(JSON.stringify(arguments));}"
;
18.
eval(code);
19.
}
20.
21.
//创建iframe,必须在创建external之后,否则会出现死循环
22.
_createQueueReadyIframe(document);
23.
//通知js开始初始化
24.
//initReady();
25.
})();
01.
- (
void
)webViewDidFinishLoad:(UIWebView *)webView {
02.
//js是否注入成功
03.
if
(![[webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@
"typeof window.%@ == 'object'"
, kBridgeName]] isEqualToString:@
"true"
]) {
04.
NSBundle *bundle = _resourceBundle ? _resourceBundle : [NSBundle mainBundle];
05.
NSString *filePath = [bundle pathForResource:@
"WebViewJsBridge"
ofType:@
"js"
];
06.
NSString *js = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
07.
[webView stringByEvaluatingJavaScriptFromString:js];
08.
}
09.
}
注意,以上代码在注入前需要判断是否已经注入,避免重复注入。接下来,我们可以再实现一个webview的委托来拦截重定向事件:
01.
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
02.
NSURL *url = [request URL];
03.
04.
NSString *requestString = [[request URL] absoluteString];
05.
if
([requestString hasPrefix:kCustomProtocolScheme]) {
06.
NSArray *components = [[url absoluteString] componentsSeparatedByString:@
":"
];
07.
08.
NSString *function = (NSString*)[components objectAtIndex:
1
];
09.
NSString *argsAsString = [(NSString*)[components objectAtIndex:
2
]
10.
stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
11.
NSData *argsData = [argsAsString dataUsingEncoding:NSUTF8StringEncoding];
12.
NSDictionary *argsDic = (NSDictionary *)[NSJSONSerialization JSONObjectWithData:argsData options:kNilOptions error:NULL];
13.
//将js的数组转换成objc的数组
14.
NSMutableArray *args = [NSMutableArray array];
15.
for
(
int
i=
0
; i<[argsDic count]; i++) {
16.
[args addObject:[argsDic objectForKey:[NSString stringWithFormat:@
"%d"
, i]]];
17.
}
18.
//调用oc方法,忽略警告
19.
#pragma clang diagnostic ignored
"-Warc-performSelector-leaks"
20.
SEL selector = NSSelectorFromString([function stringByAppendingString:@
":"
]);
21.
CLog(@
"sel:%@, args:%@"
, function, args);
22.
if
([self respondsToSelector:selector]) {
23.
[self performSelector:selector withObject:args];
24.
}
25.
return
NO;
26.
}
else
{
27.
return
YES;
28.
}
29.
}
但以上方法的缺点是还没有达到Android中的方便程度,最理想的方法是当进行js注入时,动态设置待绑定的对象,同时动态获取当前类的实例方法,并生成相应的js代码。这就完全实现了Android中的绑定机制。这个实现起来并不难,有兴趣的朋友可以试试。
为了方便大家使用,我将以上代码封装成了一个类,具体的使用方法见Demo。
需要注意的是:
1. 在实际应用中可能出现js执行顺序的问题,如果网页中的js在注入前先获取绑定的对象进行保存,是无法获取到的,因为这时候待绑定的对象为空。这就需要网页中js的初始化在注入之后,因此在上文代码中有一个initReady方法。
2. 由于performSelector最多只能包含两个参数,因此例子中是通过数组来传递参数列表的。
如果大家觉得对自己有帮助的话,还希望能帮顶一下,谢谢:)个人博客:http://blog.csdn.net/zhaoxy2850本文地址:http://blog.csdn.net/zhaoxy_thu/article/details/22794201转载请注明出处,谢谢!
联系客服