您的位置:首页 > 移动开发 > Objective-C

Objective C 和 Javascript 之间的互相调用

2016-06-08 17:55 567 查看
有些时候出于某些原因(web界面更新迭代快、工作分离),我们需要让手机里面的网页能调用Objective C的原生代码或者是反过来。 所以这时候就需要解决Objective C 和 页面代码(一般就是Javascript)之间互相调用、通信的问题了。

那么, 首先我们从简单的方面开始讲吧,Objective C如何调用Javascript代码

Objective
C如何调用Javascript代码?

这个很简单, UIWebView有个方法是:

- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script


就能直接调用了。例如你想获取页面document的title属性,那么只需要: 
NSString *title = [webview stringByEvaluatingJavaScriptFromString:@"document.title"]];


又或者想调用页面的一个叫
test
的函数,则只需要 
[webview
stringByEvaluatingJavaScriptFromString:@"test()"]


注意事项: JS代码占用的内存使用量不能超过10M。

Javascript如何调用Objective
C代码?它们怎么通信呢?

iOS里面加载一个网页用的是UIWebView,而关于页面加载的情况是通过UIWebView的一个Delegate:UIWebViewDelegate来通知
对应的webview的。而每次点击页面上的链接(或者是加载本页面的地址时)都会在加载前调用UIWebViewDelegate的一个方法: 
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest
*)request navigationType:(UIWebViewNavigationType)navigationType
 如果这个方法的返回值是YES的话就继续加载这个请求,如果是NO的话就不加载了。 所以Javascript调用Objective C代码的秘诀就在这个方法里面了。

Step
1. 匹配url格式

伪代码一般如下:

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:   (UIWebViewNavigationType)navigationType
{
if (request.URL.absoluteString match urlSchemePattern) {
[self executeSomeObjectiveCCode];
return NO;
} else {
return YES;
}
}

request.URL.absoluteString match urlSchemePattern
 这里就是如果页面的url的格式是满足某种特定格式的话就不加载那个请求,而是执行Objective
C的代码。

Step
2. 协商url格式以及参数传递方式

现在很明显的是, 一般Javascript想要调用Objective C代码时,Javascript代码就需要和Objective C协商一个请求的协议,例如:凡是请求的url scheme 是
"js-call://"
 这样格式开头的就是Javascript需要调用Objective
C的代码,再具体点,比如"js-call://user/get" 就是要调用Objective C 代码中一个getUser的方法的。(这里的js-call只是样例,实际中你可以自定义其他的字符串和格式,例如myjs:///也是可以的) 那么如果Javascript需要传递参数给Objective C呢? 很自然的,这里我们想到最简单的方法是像http的query string一样传参数(网上也有实现用json传的), 例如:"js-call://user/set?uid=1&name=jpx",然后在分析url的时候将query
string提取出来传给Objective C的方法即可。 伪代码如下:

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
if ([request.URL.absoluteString hasPrefix:@"js-call://user/set"]) {
NSDictionary *parameters = [self parseQueryString:request.URL.absoluetString];
[self executeSomeObjectiveCCodeWithParameters:parameters];
return NO;
} else if ([request.URL.absoluteString hasPrefix:@"js-call://user/get"]) {
NSDictionary *parameters = [self parseQueryString:request.URL.absoluetString];
[self executeSomeObjectiveCCodeWithParameters:parameters];
return NO;
}
return YES;
}

OK, 那么现在Javascript调用Objective C的方法就讲完了。不过认真思考的同学可能会想到,如果我的Javascript需要调用好几个 Objective C的接口,那么在shouldStartLoadWithRequest的delegate方法里面不就很多if ... elif ... else ...的代码!!! 而且解析query string的那部分代码也是重复的!!!别的ViewController要实现这样的一套Javascript调用Objective C机制时
有得重复写这些代码?!So 作为程序员的我,也思考了这样的问题,我的解决办法是将这一切封装起来,于是JPXUIWebViewJSBridge就诞生了。

介绍JPXUIWebViewJSBridge

由于我不想对url的格式分析是很多的if else 代码,而且希望这部分代码能重用。所以我参考了以前用过的某Web框架的url dispatch的机制优化。 大概的思想就是我们定义url格式应该匹配到哪个cgi handler,大概是这样的:

//r'login' 就是正则表达式匹配请求的url的,后面的login对应的是cgi handler的login函数。
url_config = [
(r'login', 'login'),
(r'user/set', 'update_user'),
]

所以我的JPXUIWebViewJSBridge也是可以这样做的:

self.bridge = [[JPXUIWebViewJSBridge alloc] initWithHandler:self];
self.bridge.routines = @[@[@"^js-call://user/set.*$", @"setUser"],
@[@"^js-call://user/get.*$", @"getUser"]
];

是不是很方便呢?定义了这套规则之后,只需要比如说在ViewController里面实现一个叫setUser的方法即可: 
- (void)setUser:(NSDictionary *)parametersFromWeb
 ,
其中parametersFromWeb就是query string对应的字典!

然后在 
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
 只需要这样写就可以了:

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSError *error;
BOOL canHandleRequest = [self.bridge canHandleRequest:request error:&error];
if (canHandleRequest) {
[self.bridge handleRequest:request error:&error];
NSLog(@"error1:%@", [error localizedDescription]);
return NO;
} else {
NSLog(@"error2:%@", [error localizedDescription]);
}
return YES;
}

具体可以看这里的Demo 。 欢迎有需要的同学使用!

一些注意事项

Javascript调用Objective C时,很多人第一反应就是在a标签里面的href写url调用,例如: 
<a href="js-call://user/set?uid=1&name=jpx" >测试</a>
,
但是这样的调用会如下的一些问题:

There is weird but apprehensible bugs with this practice:
a lot of javascript/html stuff get broken when we cancel a location change:

All setInterval and setTimeout immediatly stop on location change
Every innerHTML won’t work after a canceled location change!
You may get other really weird bugs, really hard to diagnose...
而更加合理的做法应该是通过加载一个iframe:

function execute(url)
{
var iframe = document.createElement("IFRAME");
iframe.setAttribute("src", url);
document.documentElement.appendChild(iframe);
iframe.parentNode.removeChild(iframe);
iframe = null;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: