您的位置:首页 > 移动开发 > IOS开发

phonegap源码分析(三)------ IOS

2012-07-25 16:13 260 查看
几个月前看过phonegap在Android和WP上的实现源码,当时苦于没mac环境,直到现在才抽出时间学习了一下phonegap是如何让JS与native串联起来的。

phonegap在IOS上和在WP很类似,由于IOS App内置的WebBrowser提供了比较好的与JS的互通机制,所以整个代码读起来比较轻松,架构图如下所示:



其中有这么几个点值得强调一下:

Phonegap IOS的项目结构及初始化过程
JS调用Native
Native向JS返回结果

1)Phonegap IOS的项目结构及初始化过程

Phonegap IOS的项目结构非常简单,其实就是一个标准的单View的IOS App。只不过这个单view的视图文件xib是空的,它在MainViewController里添加了一个UIWebView,通过这个UIWebView来展现www里的html。

Phonegap的初始化过程包含JS端和Native端,这两端都是基于事件侦听的方式结合起来,JS端主要包含以下几个点:

onDOMContentLoaded:dom载入完成
onNativeReady:Native端WebUI载入完成
onCordovaReady:JS端相关objects都创建完成

deviceready:整个phonegap初始化完成

以上几个重要事件的先后顺序和hander侦听是通过channel组件架构,所谓的channel组件实际上就是phonegap***的保障事件侦听和触发的组件,这块代码写得不错,短短2百多行代码,就打造了一个JS端事件侦听的框架,有兴趣的同学值得读一读。其中 onNativeReady是被native端调的,当native端WebUI初始化好后就会fire JS端onNativeReady事件,下面来看看native的几个关键的初始化节点:

AppDelegate.didFinishLaunchingWithOptions:App启动,初始化controller和view
CDVViewController.viewDidLoad:view加载,初始化WebView
CDVViewController.webviewDidFinishLoad:WebView加载,触发JS端onNativeReady

Native端存在着App->view->webview三个层次,以上三个点正好对应着这三个层次的加载。

2)JS调用Native

IOS的UIWebViewDelegate提供了shouldStartLoadWithRequest方法,它能截获web端url请求,因此phonegap就是通过在web端构造一个不可见的iframe,并置其src为gap://ready,Native端截获这个请求后就会得知此时JS端有请求。这块代码可见"cordova/exec"模块:

createGapBridge = function() {

        gapBridge = document.createElement("iframe");
        gapBridge.setAttribute("style", "display:none;");
        gapBridge.setAttribute("height","0px");
        gapBridge.setAttribute("width","0px");
        gapBridge.setAttribute("frameborder","0");
        document.documentElement.appendChild(gapBridge);
    }
那么具体的调用信息是如何传到native的呢?实际上是每次在js端调用exec时,phonegap会把调用信息放入cordova.commandQueue队列中,并通知native端。native端得到通知后,会调用js端"cordova/plugin/ios/nativecomm"模块里的代码拿到cordova.commandQueue队列中所有调用信息,并依次调用plugin来执行请求,源码如下所示:
JS端:

cordova.commandQueue.push(JSON.stringify(command));
if (cordova.commandQueue.length == 1 && !cordova.commandQueueFlushing) {
    if (!gapBridge) {
        createGapBridge();
    }
    gapBridge.src = "gap://ready";
}


Native端:shouldStartLoadWithRequest
if ([[url scheme] isEqualToString:@"gap"]) {
    [self flushCommandQueue];
    return NO;
}


- (void) flushCommandQueue
{
    [self.webView stringByEvaluatingJavaScriptFromString:
	 @"cordova.commandQueueFlushing = true"];
	
    // Keep executing the command queue until no commands get executed.
    // This ensures that commands that are queued while executing other
    // commands are executed as well.
    int numExecutedCommands = 0;
    do {
        numExecutedCommands = [self executeQueuedCommands];
    } while (numExecutedCommands != 0);
	
    [self.webView stringByEvaluatingJavaScriptFromString:
	 @"cordova.commandQueueFlushing = false"];
}


- (int) executeQueuedCommands
{
    // Grab all the queued commands from the JS side.
    NSString* queuedCommandsJSON = [self.webView stringByEvaluatingJavaScriptFromString:
									@"cordova.require('cordova/plugin/ios/nativecomm')()"];
	
	
    // Parse the returned JSON array.
    NSArray* queuedCommands =
	[queuedCommandsJSON cdvjk_objectFromJSONString];
	
    // Iterate over and execute all of the commands.
    for (NSString* commandJson in queuedCommands) {
		
        if(![self.commandDelegate execute:
		 [CDVInvokedUrlCommand commandFromObject:
		  [commandJson cdvjk_mutableObjectFromJSONString]]])
		{
            static NSUInteger maxLogLength = 1024;
            NSString* commandString = ([commandJson length] > maxLogLength) ? 
                [NSString stringWithFormat:@"%@[...]", [commandJson substringToIndex:maxLogLength]] : 
                commandJson;
			DLog(@"FAILED pluginJSON = %@", commandString);
		}
    }
	
    return [queuedCommands count];
}


这几段代码就算没学过Objective-C也应该能猜出个大概,这也算是一个供应者和消费者模式的应用实例。
3)Native向JS返回结果

上段代码也透露了Native调用JS的方式:

self.webView stringByEvaluatingJavaScriptFromString
有了这个便利的方法,可以避免像在Android端使用ajax和polling这么复杂的实现。对于一个标准的phonegap的调用请求,native的plugin完成任务后,会统一调用JS端cordova.callbackSuccess和cordova.callbackError,见CDVPluginResult.m:
-(NSString*) toSuccessCallbackString: (NSString*) callbackId
{
	NSString* successCB = [NSString stringWithFormat:@"cordova.callbackSuccess('%@',%@);", callbackId, [self toJSONString]];			
	
	DLog(@"PluginResult toSuccessCallbackString: %@", successCB);
	return successCB;
}

-(NSString*) toErrorCallbackString: (NSString*) callbackId
{
	NSString* errorCB = [NSString stringWithFormat:@"cordova.callbackError('%@',%@);", callbackId, [self toJSONString]];
	

	DLog(@"PluginResult toErrorCallbackString: %@", errorCB);
	return errorCB;
}


到此,Phonegap在IOS平台上的实现比较关键的几个点已分析完,后面我会基于之前对android和wp源码分析一起,来看看这样的实现方式会有那些限制和性能损耗
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: