一、分析的目标
- 了解Titanium产品的基本框架结构和特点
- 了解Titanium产品如何扩展本地API以及访问方式
- 了解Titanium产品中的动态语言之间如何相互调用
二、Titanium概述
2.1 Titanium介绍
Titanium是一个Web应用程序运行环境,它支持不同的系统平台(Windows、Linux、Mac),并且支持Web应用程序对本地APIs的访问。在基于Titanium平台上,用户可以快速开发和方便的部署应用程序,并且这些应用程序可以使用本地APIs实现许多普通Web应用程序无法完成的功能和特性。
2.2 Titanium特点
Titanium框架具有如下几个方面的特点:
- 支持多平台(Linux、Mac、Windows、移动设备)
- 使用Web技术加快软件开发速度
- 支持Web中内嵌多种编程语言
- 支持对本地APIs的访问
- 通过Appcelerator网络云服务,基于Titanium的应用可以更容易的打包、测试和部署
- 本地功能的模块化,可动态加载指定的功能模块
- 强大灵活的语言扩展,用户在Titanium框架中可以很方便的扩展多种动态语言
2.3 Titanium 框架结构
上图来自于Appcelerator官网,该图以iPhone和Android两个移动平台为例,描述了Titanium的总体框架结构。在Titanium框架中,Web应用程序可以很方便的访问设备UI组件。比如,可以在页面中使用Titanium提供的API控制导航条、工具栏、菜单,以及可以动态的向用户弹出对话框、警告框等。除此,之外Titanium API还支持本地功能模块的访问,即用户可以使用Titanium提供的APIs接口访问数据库、定位功能、文件系统功能、网络功能、媒体功能等。
不过该框架图,并没有将Titanium中对多种脚本语言的相互访问机制很好的表现出来。但是,这一机制却又是Titanium框架的一个比较重要的功能特性。
三、Titanium构建
Titanium的构建过程使用scons管理(http://www.scons.org/)。scons是一个开源的软件构建工具,使用Python语言来描述软件构建规则。通过Titanium的源码级构建和Titanium的构建规则两个方面,可以了解Titanium运行环境由那些部分组成、这些模块和模块之间的关系是什么。
[注]以下所有的测试和分析内容均是以Linux平台上Desktop版本的Titanium代码为基础。
构建Titanium所依赖的库和环境
- Ruby 1.8.x 开发包
- Python 2.5.x开发包
- scons构建工具
- git 版本管理工具
Ubuntu 9.04上构建Titanium所需的支持包
- sudo apt-get install build-essential ruby rubygems libzip-ruby \
- scons libxml2-dev libgtk2.0-dev python-dev ruby-dev \
- libdbus-glib-1-dev libnotify-dev libgstreamer0.10-dev \
- libxss-dev libcurl4-openssl-dev
- sudo apt-get install git-core
获取Titanium源码
- git clone git://github.com/marshall/titanium
- cd titanium
获取Kroll源码
- git submodule init
- git submodule update
- cd kroll
- git checkout master
构建Titanium测试程序
- cd ..
- scons debug=1
运行
- scons testapp debug=1 run=1
有关Titanium构建相关的信息,可以访问以下页面获得:
http://wiki.github.com/marshall/titanium/build-instructions
3.2 Titanium构建规则分析
3.2.1 版本需求
构建过程所需的库/程序版本 | |
Python | 2.5 |
Ruby | 1.8 |
Scons | 1.2 |
kroll 源码版本 | 12/30/99 |
titanium_desktop 源码版本 | 12/30/99 |
WebKit版本 | libwebkittitanium-1.0.so.2.7.0 |
3.2.2 默认配置项
默认配置项 | ||
配置 | 值 | 备注 |
PRODUCT_VERSION | 0.7.0 | |
INSTALL_PREFIX | /usr/local | |
PRODUCT_NAME | Titanium | |
CONFIG_FILENAME | tiapp.xml | |
BUILD_DIR | build | |
THIRD_PARTY_DIR | kroll/thirdparty | |
DISTRIBUTION_URL | api.appcelerator.net | |
CRASH_REPORT_URL | api.appcelerator.net/p/v1/app-crash-report | |
GLOBAL_NS_VARNAME | Titanium | 定义了全局Titanium对象名称 |
3.2.3 scons编译参数
Scons编译参数 | |
debug | 0表示release版本,1表示debug版本 |
clean | 清除构建的工程 |
qclean | 清除构建的工程 |
run | 运行TestApp |
run_with | 带参数运行TestApp,好像Linux平台上没用 |
3.2.4 构建规则文件
构建规则文件 | |
kroll/SConscript.thirdparty | Titanium所需的第三方支持文件规则 |
installation/SConscript | Titanium安装器构建规则 |
kroll/SConscript | 构建kroll库规则 |
modules/SConscript | 构建语言支持模块规则 |
apps/SConscript | 构建TestApp规则 |
SConscript.dist | 构建SDK规则 |
SConscript.docs | 构建APIs文档规则 |
SConscript.test | 构建测试程序规则 |
3.2.5 核心库和程序构建规则
库/程序 | 规则 |
build/linux/runtime/template/kboot | kroll/boot/breakpad/common/*.c
kroll/boot/breakpad/common/*.cc kroll/boot/breakpad/client/*.cc kroll/boot/breakpad/processor/*.cc kroll/boot/breakpad/client/linux/handler/*.cc kroll/boot/breakpad/common/linux/*.cc |
build/linux/runtime/libkroll.so | kroll/api/*.cpp
kroll/api/config/*.cpp kroll/api/binding/*.cpp kroll/api/utils/*.cpp kroll/api/utils/poco/*.cpp kroll/api/utils/linux/*.cpp kroll/api/net/proxy_config.cpp kroll/api/net/*_linux.cpp |
build/linux/runtime/libkhost.so | kroll/host/linux/host.cpp
kroll/host/linux/linux_job.cpp |
/linux/modules/api/libapimodule.so | poco third library(http://pocoproject.org/)
kroll/modules/api/*.cpp |
build/linux/modules/javascript/libjavascriptmodule.so | poco third library(http://pocoproject.org/)
webkittitanium-1.0 third library kroll/modules/javascript/*.cpp |
build/linux/modules/ruby/librubymodule.so | poco third library(http://pocoproject.org/)
libruby third library kroll/modules/ruby/*.cpp |
build/linux/modules/php/libphpmodule.so | poco third library(http://pocoproject.org/)
kroll/modules/php/*.cpp |
四、Titanium静态分析
该部分主要是说明整个Titanium的阅读工作量、弄清楚Titanium中定义的核心对象的功能作用,以及各个模块之间的关系是什么。
4.1 代码统计
这里,将Titanium项目代码分成kroll和功能模块扩展两部分代码来统计,数据如下两表所示:
Kroll模块代码量统计 | ||||||
Language | Files | Blank | Comment | Code | Scale | Equiv |
C/C++ Header | 1168 | 35490 |
63506 |
111461 |
1.00 |
111461 |
HTML | 386 | 1252 | 16112 | 51375 | 1.9 | 97612.5 |
C++ | 162 | 6401 | 7046 | 33133 | 1.51 | 50030.83 |
Javascript | 47 | 3273 | 1598 | 13214 | 1.48 | 19556.72 |
CSS | 3 | 554 | 41 | 2720 | 1 | 2720 |
Object C | 6 | 359 | 312 | 1400 | 2.96 | 4144 |
Python | 10 | 260 | 185 | 1206 | 4.2 | 5065.2 |
Shell | 11 | 56 | 157 | 234 | 3.81 | 891.54 |
Make | 3 | 30 | 29 | 93 | 2.5 | 232.5 |
Assembly | 1 | 15 | 39 | 57 | 0.25 | 14.25 |
Ruby | 1 | 10 | 0 | 54 | 4.2 | 226.8 |
Yaml | 1 | 0 | 0 | 12 | 0.9 | 10.8 |
SUM | 1802 | 47938 | 89263 | 217012 | 1.35 | 293546.95 |
titanium_desktop模块(排除Kroll模块) | ||||||
Language | Files | Blank | Comment | Code | Scale | Equiv |
Javascript | 118 | 5801 | 3276 | 28678 | 1.48 | 42443.44 |
C++ | 125 | 4690 | 5169 | 27320 | 1.51 | 41253.2 |
C/C++ Header | 159 | 1647 | 3443 | 7682 | 1 | 7682 |
HTML | 49 | 347 | 39 | 3715 | 1.8 | 7058.5 |
Ruby | 29 | 673 | 643 | 3227 | 4.2 | 13553.4 |
CSS | 5 | 542 | 41 | 2655 | 1 | 2655 |
Python | 45 | 601 | 664 | 2632 | 4.2 | 11054.4 |
C | 1 | 167 | 237 | 1925 | 0.77 | 1482.25 |
Shell | 13 | 60 | 158 | 251 | 3.81 | 956.31 |
PHP | 5 | 37 | 1 | 179 | 3.5 | 626.5 |
XML | 5 | 0 | 8 | 151 | 1.9 | 286.9 |
Object C | 2 | 31 | 15 | 119 | 2.96 | 352.24 |
SUM | 556 | 14596 | 13694 | 78534 | 1.65 | 129404.14 |
4.2 核心对象的介绍
对象 | 基类 | 说明 |
AccessorBoundObject | StaticBoundObject | 对setter和getter的封装,当用户访问想访问XXX属性时,该对象会调用setXXX方法或者getXXX方法。目前Titanium中主要是JS的Titanium对象使用AccessortBoundObject封装 |
AccessorBoundMethod | StaticBoundMethod | 用于通过属性的方式访问方法,由该对象封装的方法,会自动的导出setter和getter方法 |
AccessorBoundList | StaticBoundList | 用于以属性的方式访问list对象,由该对象封装的list,会自动导出setter和getter |
ArgList | 对参数列表对象的封装 | |
Blob | 对数据封装,可以描述任何数据 | |
Tuplex | 对元组对象的封装 | |
DelegateStaticBoundObject | KObject | 用于对全局访问对象的封装,目前Titanium中只有UI和Titanium JS对象使用该对象封装 |
KList | KObject | 封装List对象 |
KMethod | KObject | 对方法的封装,所有扩展语言的函数,都需要用该对象封装 |
KEventObject | AccessorBoundObject | 描述事件对象,JS中可以通过该对象,向主线程发送事件。比如重新载入页面、弹出对话框。 |
KEventMethod | KEventObject | 对事件方法的封装,目前只有ti.Process模块使用该对象 |
KObject | ReferenceCounted | 所有的其他类型语言对象和方法都是继承该类,这样可以按照相同的方法处理不同语言对象和方法 |
StaticBoundList | KList | 静态列表,使用内部map绑定属性 |
StaticBoundMethod | KMethod | 静态方法 |
StaticBoundObject | KObject | 静态对象,继承该对象可以很方便的设置对象的属性、方法。
每个StaticBoundObject内部,都保存着一个String到ShareValue的map成员属性。 |
Value | ReferenceCounted | 描述对象类型 |
4.3 模块之间的关系
从整体框架结构上来看,可以将Titanium分成三个部分,最上层是WebKit以及针对WebKit的扩展(修改很少),中间层是kroll可以将其看成是一个中间件,最下层是个个模块的扩展。模块之间的关系如图所示:
点击查看大图
以下从WebKit、Kroll和模块扩展三个部分来说明
1、WebKit: 当WebKit引擎解析页面数据发现
首先,WebCore引擎会解析HTML页面数据,当发现有标签,或者当用户触发了页面中某个与脚本函数相关的控件时,WebCore会将相应的脚本代码片段传递给JavascriptCore解析执行。如果对比Tinanium修改的WebKit代码和原始的WebKit代码(http://www.webkit.org)会发现,tinanium对WebKit的修改是及小的。主要是作了两个方面的工作:首先,tinanium扩展了KURL的处理,增加了ti://, app://等私有协议的支持。再者,在WebKit/gtk/webkit/目录中,添加了几个接口函数(主要是用来处理扩展的协议和注册解析器),其中最重要的是webkit_titanium_add_script_evaluator,该接口在Kroll模块的script类中会被调用,用来向WebKit引擎注册一个Evaluator Proxy。
2、Kroll和Base Module:这部分主要的职责是负责Javascript的方法、对象和Python、Ruby、PHP等语言之间相互转换、事件处理,以及模块动态加载。Kroll模块中,定义了一个host对象,这个对象是整个TestApp的主线程,UI初始化、WebKit初始化和事件处理都是在host中完成的。host对象中保存了一个全局对象表,该表会在WebKit引擎、Python引擎、Ruby引擎之间以KObject中间对象形式相互传递,最终达到不同语言之间的相互调用。
3、API Extension:这里扩展了大量的与系统平台功能相关的API共Web应用使用。其中最重要的一个对象是ti.UI,该模块负责UI相关的资源、事件处理、GTK主界面的创建、Tininum JS对象的创建。
五、Titanium动态分析
下面从6个方面以TestApp为例,来分析Titanium的主要特性和功能。
5.1 TestApp初始化
TestApp的启动过程有个自启动过程。首先,TestApp启动后会创建一个Application对象,该对象会从Mainifest文件中获取App相关的资源,并且保存在一个全局变量中。然后,TestApp会设置几个系统环境变量:
- KR_BOOTSTRAPPED: 描述是否已经初始化环境变量以及构建Application对象
- KR_HOME:描述运行程序的HOME路径
- KR_RUNTIME:描述运行时资源路径
- KR_MODULES:描述需加载模块信息
- LD_LIBRARY_PATH:描述模块所在的文件夹路径
***,TestApp会使用exec系统调用将自己自启,然后通过之前设置 KR_BOOTSTRAPPED环境变量判断是否进入下一阶段的初始化过程。如果 KR_BOOTSTRAPPED设置为YES,则会首先将其unset,然后启动LinuxHost。
在titanium框架中,使用动态库的方式将模块之间的关系解耦合。在TestApp启动的第二阶段中,StartHost(kroll/boot/boot_linux.cpp)会根据之前设置的 KR_RUNTIME路径信息,找到libkhost.so动态库,然后从libkhost.so中获取Execute函数指针,并且调用(这里有个问题,如果多个实列同时运行,有可能KR_RUNTIME尚未unset就启动第二个应用,则会出现TestApp异常)。
libkhost.so动态库中的Execute方法,首先创建一个Host实例,在这里是LinuxHost对象,然后调用该对象的Run方法进入一个循环。这个循环是整个TestApp的主循环,主要负责模块的动态发现和加载,事件处理。在LinuxHost的实现中,会维护一个job队列,通过定时器的方式,每隔250ms的时间会去检测该job队列中是否有job存在(LinuxJob描述)。如果,事件存在则会一次性将所有的事件取出,并且清空事件队列,然后一个个的执行job对象的Execute方法。
TestApp的两次初始化过程如下图所示:
点击查看大图
5.2 模块初始化
TestApp程序创建LinuxHost对象,并且执行Run方法之后,会首先扫描KR_MODULES环境变量中指定的模块,并且从 LD_LIBRARY_PATH定义的路径信息中寻找到这些动态库模块,并且加载(调用相应模块的Initialize方法)。LinuxHost首先加载的是基本模块(API,PythonModule、RubyModule、PHPModule和JavascriptModule)。
以pythonModule为例,描述一个完整的加载过程:
- Host::LoadModules从环境变量中获取到python动态库的路径信息
- 调用Host::FindBasicModules方法,将libpythonmodule.so文件加载进来,然后调用PythonModule的Initialize方法
- PythonModule::Initialize首先会向全局属性表中创建一个Python和PythonEaluator对象的关联。前面也说到,Host对象会保存一个全局属性表,这个表中使用KObject中间对象形式,将JAVASCRIPT、PYTHON、RUBY、PHP等语言定义的对象封装,并且保存在该表中。运行时,可以使用Host对象的GetGlobalObject方法获取。
- 调用Script::AddScriptEvaluator静态方法将PythonEvaluator对象放入Script对象中维护的一个Ealuator列表中。当JavascriptCore引擎发现<script>标签会遍历这个evaluator链表,通过MIME类型找到相应的解析器实例,然后将代码片段传递给相应的解析器处理(这样就可以支持HTML代码中内嵌多种语言)。
这里还有一个模块比较特殊需要仔细说明,即ti.UI模块。该模块负责WebKit引擎初始化,GTK窗口创建以及UI事件的处理。加载过程类似PythonModule,首先Host对象找到libtiuimodule.so动态库,然后调用Initialize方法初始化,ti.UI模块中的Initialize方法只做了两件事情,创建了一个APIBinding对象,然后将“API”属性和APIBinding关联起来,保存在全局属性表中。接下来,当Host::LoadModules方法加载完毕所有的动态库后,会调用Host::StartModules来启动模块(在整个TestApp运行中,只有ti.UI模块的Start方法被重载了,而其他模块在StartModules方法被执行时,什么事情都没有做)。UIModule::Start方法做了三个非常重要的操作:1、创建GtkUIBinding对象,并且将“UI”和该对象绑定,存放在全局属性表中。2、调用ScriptEvaluator::Initialzie()使用WebKit扩展中导出的webkit_titanium_add_script_evaluator函数,将自己注册到JavascriptCore中。3、创建初始化WebView。
至此,WebKit引擎已经初始化完毕、UI界面已经初始化完毕、相应语言的解析器以及JAVACRIPT API扩展对象已经添加到全局属性表中。但是至此,页面中是无法正常访问Titanium对象的。
整个过程如下图所示:
点击查看大图
5.3 Titanium对象的注册
Javascript中的Titanium并没有通过硬编码的方式定义该名称,而是在构建的过程中通过变量的方式指定的。在构建规则中有GLOBAL_NS_VARNAME变量,该变量名称会作为编译参数传递,代码通过改变量的定义,来确定Javascript可见的Titanium主对象的名称是什么。
当webView构建完毕后,Titanium Js主对象并不存在,只有当***次WebKit遇到脚本代码时,这个对象才被创建。当WebKit引擎碰到脚本对象时,会调用JavascriptCore的initScipt方法,初始化Javascript引擎。在此JavascriptCore会将我们之前调用webkit_titanium_add_script_evaluator增加的Evaluator代理和JavascriptCore解析关联起来。当引擎和资源都初始化完毕,会向FrameClient发送object avaliable通知,会调用FrameLoader::dispatchWindowObjectAvaliable()方法。这个方法会导致UserWindow::RegisterJSContext()方法的调用。然后UserWindow对象会创建一个DelegateStaticBoundObject对象来描述Titanium对象,并且将之前初始化完毕的Titanium API对象(KObject)与Titanium对象关联起来,然后将其放入到全局属性表中。这样,之后Web应用程序就可以从全局属性表中访问到Titanium对象了。但是Titanium对象中有哪些子属性是不知道的,这是运行时才去确定。
这个初始化过程如下图所示:
点击查看大图
5.4 事件系统
Titanium的事件系统分成两个部分来说明,一部分是如何获取事件,另一部分是如何向事件系统中增加新事件。
当linuxHost::RunLoop循环被调用后,立即会注册一个定时器,每隔250ms调用一次main_thread_job_handler函数。该函数首先通过GetJobs方法获取当前系统中未处理的事件,并且依次的调用事件对象(LinuxJob)的Execute方法执行。如果系统没有未决的事件存在,则立即返回。
事件的添加和触发均通过LinuxHost::InvokeMethodOnMainThread()方法完成。该函数会创建一个LinuxJob对象,并且插入到事件队列尾中。事件的触发有多种可能性,可以是由Javascript代码触发,也可以是内部事件触发,同样也有可能是UI事件触发。
由于Titanium扩展的JS API在C层上都会与相应的C方法对应,当Web应用程序调用相应的JS方法,对应的C方法会被调用,该方法则会使用LinuxHost::InvokeMethodOnMainThread()方法,向主线程发送事件处理请求。
对于UI来说,在Linux平台上所有的UI相关的事件首先是被GTK的应用框架截获,当有UI事件到来时,ti.UI模块会创建对应事件的KEvent对象(在event.h中定义),然后调用KEvent对象的Fire方法,触发事件。该方法会间接的使用LinuxHost::InvokeMethodOnMainThread方法向LinuxHost事件队列注册事件。
这个过程如下图所示:
点击查看大图
5.5 访问Titanium对象属性和方法
比如,有一段Javascript脚本中调用Titanium.API获取Titanium对象的API属性,当JavascriptCore解析到这部分代码时,并不知道这个对象是什么类型的,完全当作一个抽象的JSValue对象来对待,因为对于Javascript引擎来说,并不需要时刻知道对象是什么,有那些属性和方法,只有到运行时才会去用查询该对象中是否存在指定的属性或者方法(JavascriptCore内部维护了一张属性表,通过查询方式获取相应属性或者函数的处理函数指针。这点V8做的就比较高明,用类的方式描述,动态将对应JS的方法和属性转换成C++的成员函数和成员变量,大量的减少了访问时间)。由于API这个对象是由Kroll创建的,是一个KObject对象,在挂载到Javascript 全局属性表之前,Titanium会调用KObjectToJSValue方法,将KObject对象转换成一个JS的Object对象,并且设置了几个比较重要的回调函数:
- HasPropertyCallback: 当查询属性时候被调用
- GetPropertyCallback: 当获取指定属性时被调用
- SetPropertyCallback: 当设置指定属性时被调用
当javascript访问用Titanium对象的API属性时,通过JSValue.Get方法会调用到GetPropertyCallback函数,该函数会查询KObject对象中(这里是说的Titanium)是否有API这个属性,如果有则转换成JSValue对象,并且返回给Javascript引擎。
如果这里访问的是一个属性的方法,过程和访问对象是一样的,只不过***创建的是一个Function Js对象,而非Object对象。当Javascript访问这个返回的Function对象时,Javascript引擎会调用callAsFunction方法,而该方法会引发CallAsFunctionCallback回调函数被调用(该函数是静态函数在KMethodToJSValue函数中注册)。这样通过CallAsFunctionCallback这个回调接口,调用实际的KObject的Call方法,执行实际的处理函数。
属性访问过程如下图所示:
点击查看大图
5.6 Javascript、Python、Ruby动态语言间的相互调用
Titanium框架中引入了一个比较有意思的特性,即支持多种语言之间的相互调用。从实现技术角度来说,Titanium的多语言支持的设计思想,是在学习了WebKit的Binding机制而发展过来的。主要用到了JavascriptCore引擎可以动态注册Evaluator的机制。HTML语言中定义了<script>标签,用于内嵌脚本语言,该标签有个子属性type,通过该属性可以让浏览器引擎区分是什么类型的脚本。加入我们有如下的脚本代码:
- <script type=”text/python” src=”xxx.py”></script>
首先,WebCore引擎会解析HTML页面数据,当发现有<script>标签出现,则会创建HTMLScriptElement,对于script有两种处理情况,一种是如上代码通过src包含一个脚本路径,还有一种情况是定义一段代码,通过控件或者超连接的方式以事件方式触发。如果是***种情况,则会在创建HTMLScriptElement的时引发ScriptElementData::requestScript方法的调用。如果是第二种情况,则会在触发相应事件时候调用FrameLoader::executeScript方法执行脚本。最终都会调用JavascriptCore中的EvaluatorAdapter::evaluate()。由于在初始化ti.UI模块时,我们已经注册了自己的evaluator(ScriptEvaluator),因此会将获取的脚本信息传递给ScriptEvaluator,在该对象中,会通过Script::Evaluate()方法,根据传递下来脚本的MIME类型(也就是script中text字段定义的类型)派发给注册的不同解析器去执行。
这里以Javascript调用Python代码,并且Python代码中又调用了ruby代码为例子说明其调用过程。
Python代码:
- def abc():
- ruby_fun()
Ruby代码:
- def ruby_func()
- …
- end
当pythonEvaluator::Evaluate()被调用后,首先将保存的全局JS对象表转换成Python可识别的对象字典(KMethodToPyObject完成,转换成PyKMethodType的Python对象)。这样在之后编译的Python代码中就可以访问到这些对象。然后将Python代码使用Python编译器编译,并且将编译后的函数对象转换成KObject对象,插入到全局的JS对象表中(abc)。这样,Javscript,和其他语言都可以识别该对象。同样,对Ruby函数的处理,也会首先将全局JS对象表中的KObject对象转换成Ruby的对象,然后对Ruby函数进行编译,将新生成的Ruby函数对象(ruby_fun)转换成KObject对象,然后从新更新JS全局对象转换表。至此,全局JS对象表中就新增了两个KObject对象:ruby_fun, abc。
当javascript访问该abc函数对象时,按照通常方式首先调用CaAsFunctionCallback,该函数会调用KObject的Call方法,由于该KObject实际上就是一个KPythonMethod对象,因此KPythonMethod对象的Call方法会被调用。之前我们注册的Python方法是一个PyKMethodType,该类型中定义了一个方法回调函数,当Python方法被调用时,该回调函数(PyKMethod_call)会被调用。这个例子中,在Python代码里调用了ruby_fun,因此当PyKMethod_call被调用时,首先将调用的KObject对象(实际上是一个对Ruby函数对象的封装)转换成KMethod对象,然后调用Call方法。这样就通过间接调用,调用到KRubyMethod的Call方法,使得Ruby函数得到执行。
整个过程如下图所示:
点击查看大图
六、参考资源
http://www.appcelerator.com/