Objective C 和 Javascript 之间的互相调用
2016-06-08 17:55
567 查看
有些时候出于某些原因(web界面更新迭代快、工作分离),我们需要让手机里面的网页能调用Objective C的原生代码或者是反过来。 所以这时候就需要解决Objective C 和 页面代码(一般就是Javascript)之间互相调用、通信的问题了。
那么, 首先我们从简单的方面开始讲吧,Objective C如何调用Javascript代码
Objective
这个很简单, UIWebView有个方法是:
就能直接调用了。例如你想获取页面document的title属性,那么只需要:
又或者想调用页面的一个叫
注意事项: JS代码占用的内存使用量不能超过10M。
Javascript如何调用Objective
iOS里面加载一个网页用的是UIWebView,而关于页面加载的情况是通过UIWebView的一个Delegate:UIWebViewDelegate来通知
对应的webview的。而每次点击页面上的链接(或者是加载本页面的地址时)都会在加载前调用UIWebViewDelegate的一个方法:
Step
伪代码一般如下:
C的代码。
Step
现在很明显的是, 一般Javascript想要调用Objective C代码时,Javascript代码就需要和Objective C协商一个请求的协议,例如:凡是请求的url scheme 是
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的方法即可。 伪代码如下:
OK, 那么现在Javascript调用Objective C的方法就讲完了。不过认真思考的同学可能会想到,如果我的Javascript需要调用好几个 Objective C的接口,那么在shouldStartLoadWithRequest的delegate方法里面不就很多if ... elif ... else ...的代码!!! 而且解析query string的那部分代码也是重复的!!!别的ViewController要实现这样的一套Javascript调用Objective C机制时
有得重复写这些代码?!So 作为程序员的我,也思考了这样的问题,我的解决办法是将这一切封装起来,于是JPXUIWebViewJSBridge就诞生了。
所以我的JPXUIWebViewJSBridge也是可以这样做的:
是不是很方便呢?定义了这套规则之后,只需要比如说在ViewController里面实现一个叫setUser的方法即可:
其中parametersFromWeb就是query string对应的字典!
然后在
具体可以看这里的Demo 。 欢迎有需要的同学使用!
但是这样的调用会如下的一些问题:
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:
那么, 首先我们从简单的方面开始讲吧,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; }
相关文章推荐
- sun.reflect.generics.reflectiveObjects.TypeVariableImpl cannot be cast to java.lang.Class
- Fast R-CNN: Fast Region-based Convolutional Networks for object detection
- Core Foundation对象转化为Objectctive-C对象
- js 中object对象转json
- JSONObject的简单例子
- [javaSE] 面向对象(Object类toString)
- ABAP程序中Data object的可见性
- ObjectArx 相关博客地址【更新中】
- ObjectArx自定义实体入门(C++)及注意事项
- Object-Oriented Analysis and Design Using UML 翻译与学习 (序)
- Object-Oriented Analysis and Design Using UML 翻译与学习 (目录)
- 如何解决JsonObject无序的问题
- Glib GObject
- objective-C 编程全解-第08章 类NSObject和运行时系统 下
- objective-C 编程全解-第08章 类NSObject和运行时系统 中 下
- 【Android数据传递】Intent传递List和Object和List<Object>(附源码)(转)
- Objective-C Runtime 运行时之一:类与对象
- java.lang.NoClassDefFoundError: Could not initialize class org.codehaus.jackson.map.ObjectMapper
- [Android] 解决Volley中JsonObjectRequest的Post请求添加参数的问题
- iOS UIWebView中Objective-C与Javascript的深层交互(三)