phonegap原理分析-ios版
来源:互联网 发布:linux shell 嵌套 编辑:程序博客网 时间:2024/06/05 01:52
phonegap的主要亮点就是它能通过js与native进行通信,一个基本的phonegap插件的执行过程一般是这样: js接口 --> native代码 --> js回调
所以要明白phonegap的原理,就要弄明白两件事:(1)native如何调用js,(2)js如何调用native
1. native调用js
native调用js非常简洁方便,只需要 [webView stringByEvaluatingJavaScriptFromString:@"alert('hello world!')"];
并且该方法是同步的。所以phonegap解决的主要是js调用native的问题。
2. js调用native
phonegap原理的主要难点就是js如何调用native,下面我以项目中用到的一个插件为例,通过设置断点的方法一步步理解:
(1).页面上调用插件的js代码
navigator.fixedInput.showAndFocus(function(content){ alert(content);}, 'hello world', '发送');
第一个参数为回调函数
(2).通过断点我们可以知道,js后面执行了这个方法
exec(sendCallback, null, "FixedInput", "showAndFocus", [defaultVal, btnText]);
跳入exec函数,继续执行
(3).断点执行到了cordova.js的iOSExec函数,该函数主要是获取调用参数,进队列,选择调用模式。重要的是这段代码:
switch (bridgeMode) { case jsToNativeModes.XHR_NO_PAYLOAD: case jsToNativeModes.XHR_WITH_PAYLOAD: case jsToNativeModes.XHR_OPTIONAL_PAYLOAD: pokeNativeViaXhr(); break; default: // iframe-based. pokeNativeViaIframe();}
从上面的代码,我们知道cordova会采用以下2种方式的一种,来与ios native交互
通过iframe
cordova.exec往当前的html中插入一个不可见的iframe,从而向UIWebView请求加载一个特殊的URL,这个URL里包含了要调用的native plugin的类名,方法名,参数,回调函数等信息。接下来,由于被请求加载URL,于是UIWebViewDelegate的这个方法被调用:
- (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
这样就进入了native侧代码,通过request参数就拿到了js端传过来的信息,然后调用到native plugin
通过XHR
cordova.exec里直接发起一个XHR请求,被native侧的NSURLProtocol拦截,于是调用native的这个方法:
+ (BOOL)canInitWithRequest:(NSURLRequest*)theRequest
也进入了native侧代码,然后以同样的方式调用到native plugin
在2种方式中,cordova会优先选择XHR方式,只有当XHR方式不可用时,才会使用iframe的方式。
native代码
下面以iframe方式为例,看native的执行过程
我们知道js会创建一个iframe并发送gap://ready这个指令来告诉native开始执行操作。首先是CDVViewController的shouldStartLoadWithRequest方法
CDVViewController
- (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType{ NSURL* url = [request URL]; /* * 判断url的协议以"gap"开头 * 执行在js端调用cordova.exec()的command队列 * 注:这里的command表示js调用native */ if ([[url scheme] isEqualToString:@"gap"]) { /** * 从js端拉取command,即存储在js端commandQueue数组中的数据 */ [_commandQueue fetchCommandsFromJs]; [_commandQueue executePending]; return NO; } ...}
看下CDVCommandQueue中的fetchCommandsFromJs方法与executePending方法中做的事。
CDVCommandQueue
- (void)fetchCommandsFromJs { // 获取js端存储的command,并在native暂存 NSString* queuedCommandsJSON = [_viewController.webView stringByEvaluatingJavaScriptFromString: @"cordova.require('cordova/exec').nativeFetchMessages()"]; [self enqueueCommandBatch:queuedCommandsJSON]; }
etchCommandsFromJs方法比较简单,就是从js端队列取出一条command,并转成cordova格式的对象。
executePending方法稍微复杂些,因为js是单线程的,而iOS是典型的多线程,所以executePending方法做的工作主要是让command一个一个执行,防止线程问题。
executePending方法其实与之后的execute方法紧密相连,这里一起列出,只保留关键代码:
- (void)executePending { ... //_queue即command队列,依次执行 while ([_queue count] > 0) { ... //取出从js中获取的command字符串,解析为native端的CDVInvokedUrlCommand类 CDVInvokedUrlCommand* command = [CDVInvokedUrlCommand commandFromJson:jsonEntry]; ... //执行command [self execute:command]) ... } } - (BOOL)execute:(CDVInvokedUrlCommand*)command { ... BOOL retVal = YES; //获取plugin对应的实例 CDVPlugin* obj = [_viewController.commandDelegate getCommandInstance:command.className]; //调用plugin实例的方法名 NSString* methodName = [NSString stringWithFormat:@"%@:", command.methodName]; SEL normalSelector = NSSelectorFromString(methodName); if ([obj respondsToSelector:normalSelector]) { //消息发送,执行plugin实例对应的方法,并传递参数 objc_msgSend(obj, normalSelector, command); } else { // There's no method to call, so throw an error. NSLog(@"ERROR: Method '%@' not defined in Plugin '%@'", methodName, command.className); retVal = NO; } ... return retVal; }
可以看到js调用native plugin最终执行的是objc_msgSend(obj, normalSelector, command);这块代码,设置断点看下参数
可以看到里面的参数都是都是与插件有关的,代码接着会执行具体的插件代码,路由过程我们就不去看了,代码最终执行了FixedInput.m里的showAndFocus方法
最后的运行效果:
还有一个问题,就是我们设置了回调函数,那么我们点击发送的时候,怎么将内容通过回调函数传给js呢?
看这段代码:
-(void) sendContent { NSString *inputText = textField.text; CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString : inputText]; [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]]; [self.commandDelegate sendPluginResult:pluginResult callbackId:showCommand.callbackId]; [textField resignFirstResponder]; textField.text = @"";}
我们给按钮绑定了这个函数,我们可以看到,这个方法通过下面的代码调用回调函数
[self.commandDelegate sendPluginResult:pluginResult callbackId:showCommand.callbackId];
继续跟踪代码:
- (void)sendPluginResult:(CDVPluginResult*)result callbackId:(NSString*)callbackId{ CDV_EXEC_LOG(@"Exec(%@): Sending result. Status=%@", callbackId, result.status); // This occurs when there is are no win/fail callbacks for the call. if ([@"INVALID" isEqualToString : callbackId]) { return; } // This occurs when the callback id is malformed. if (![self isValidCallbackId:callbackId]) { NSLog(@"Invalid callback id received by sendPluginResult"); return; } int status = [result.status intValue]; BOOL keepCallback = [result.keepCallback boolValue]; NSString* argumentsAsJSON = [result argumentsAsJSON]; NSString* js = [NSString stringWithFormat:@"cordova.require('cordova/exec').nativeCallback('%@',%d,%@,%d)", callbackId, status, argumentsAsJSON, keepCallback]; [self evalJsHelper:js];}
这段代码整合参数拼凑出一句js代码,最后通过evalJsHelper:js执行,设置断点我们可以知道最后js变量的内容为:
cordova.require('cordova/exec').nativeCallback('FixedInput158086966',1,\n \"hello world\"\n,1)
所以最后native会调用cordova.require(‘cordova/exec’).nativeCallback()执行我们设置的回调函数。
phonegap ios版的原理分析就到此,有理解不到位的地方欢迎指出,一起探讨。^_^
参考文章:
http://itindex.net/detail/50630-cordova-ios-native
http://www.cocoachina.com/industry/20140623/8919.html
- phonegap原理分析-ios版
- iOS版PhoneGap原理分析
- iOS版PhoneGap原理分析
- PhoneGap原理分析(Android版)
- PhoneGap原理分析(Android版)
- phoneGap在iOS下运行原理简单分析
- phonegap源码分析(三)------ IOS
- phonegap源码分析(三)------ IOS
- phoneGap分析
- phoneGap + ios
- PhoneGap iOS
- ios-phonegap
- Android phoneGap 源码分析之js-android通讯原理
- Android phonegap 到iOS phonegap
- PhoneGap 2.x ios 版使用
- IOS版添加phonegap--paypal插件教程
- IOS版添加phonegap--插件写法教程
- PhoneGap学习-执行原理
- 正则表达式
- 关于iOS设备处理器的指令集
- Android应用开发(一):Android平台搭建与开发环境配置
- iOS图片填充UIImageView(contentMode)
- C++ 之类型转换操作符
- phonegap原理分析-ios版
- 19Remove Nth Node From End of List
- 安装vncserver, vncviewer--远程桌面
- 交叉编译安装
- 覆盖索引VS非覆盖索引
- oracle日期时间段查询遇到问题to_date ora-01847 day of month must be between 1 and last day of month
- 【记忆化DFS】HDOJ1242 Rescue
- C# Winform跨线程更新UIkongjian
- C语言多文件编译