使用Titanium来开发iPhone应用,不使用Object-C而是使用JavaScript,这看起来是不可思议的事情,那么Titanium又是如何来执行JavaScript文件的呢?我们来跟踪一下自应用启动开始到执行到入口js(app.js)的TItanium代码吧!
要跟踪代码的前提是需要有生成好的代码,所以我们需要新建一个Titanium工程,然后编译生成iPhone代码。编译后在/build/iphone/下就能够找到源代码了。
由于版本的不一样,编译出来的代码有可能也不一样,我使用的版本是1.7.1。而且,代码有可能会很长,在一些地方我会省去没有关系的代码。
[size=large][b]1、/build/iphone/main.m[/b][/size]
这里从代码UIApplicationMain(argc, argv, nil, @"TiApp")可以看出来我们需要进一步进入到TiApp.mm中查看代码。
TiApp是应用在启动的时候被调用的,所以继续看application:didFinishLaunchingWithOptions方法。
[size=large][b]2、/build/iphone/Classes/TiApp.mm[/b][/size]
这里可以看出是通过[self boot]来启动的,所以继续看[self boot]。
[size=large][b]3、/build/iphone/Classes/TiApp.mm[/b][/size]
注意这里的代码[kjsBridge boot:self url:nil preload:nil];
kjsBridge是KrollBridge类的变量,所以继续查看KrollBridge的boot方法。
[size=large][b]4、/build/iphone/Classes/KrollBridge.mm[/b][/size]
继续查看[context start];。
[size=large][b]5、/build/iphone/Classes/KrollContext.mm[/b][/size]
继续查看通过@selector(main)指定的方法。
[size=large][b]6、/build/iphone/Classes/KrollContext.mm[/b][/size]
继续查看[delegate performSelector:@selector(didStartNewContext:) withObject:self]。关于KrollBridge的boot的delegate、是通过以下方法的context.delegate设定的内容:
[size=large][b]7、/build/iphone/Classes/KrollBridge.mm[/b][/size]
self类型就是KrollBridge类,继续查看KrollBridge的didStartNewContext方法。
[size=large][b]8、/build/iphone/Classes/KrollBridge.mm[/b][/size]
[size=large][color=red][b]在这里读入app.js文件。[/b][/color][/size]
继续看[self evalFile:[startURL absoluteString] callback:self selector:@selector(booted)] 这段代码。
这里@selector(evalFileOnThread:context:)指定的方法代码如下:
通过TiCheckScriptSyntax对JavaScript的语法进行验证,然后利用TiEvalScript来执行JavaScript代码。
TiEvalScript是在/build/iphone/headers/TiCore/TiBase.h中定义的。
JS_EXPORT是下边这样的,通过共享Library读入的。
[size=large][b]9、/build/iphone/headers/TiCore/TiBase.h[/b][/size]
关于TiEvalScript的代码在/build/iphone下不存在、查找[url=https://github.com/appcelerator/titanium_mobile]https://github.com/appcelerator/titanium_mobile[/url] 之后,在
titanium_mobile/iphone/SConstruct 中有如下的说明:
[quote]10:# NOTE: this is simply a pre-built version of the source at http://github.com/appcelerator/tijscore
11:# since this is so freaking complicated to setup and build in an stable environment, and since
12:# it takes like an hour to build the library, we have, as a convenience, pre-built it from the
13:# exact same source and are providing the pre-compiled versions for i386/arm[/quote]
[url=http://github.com/appcelerator/tijscore]http://github.com/appcelerator/tijscore[/url] 里有实际的源代码。
[size=large][b]10、/TiCore/API/TiBase.cpp[/b][/size]
在tijscore的README中,有如下的说明,可见Titanium Mobile对于JavaScript的执行使用了WebKit的KJS。
****传统上,WebKit包含一个网页引擎WebCore和一个脚本引擎JavaScriptCore,它们分别对应的是KDE的KHTML和KJS。
[quote]This is a Titanium Mobile fork of WebKit KJS. All changes are made available
under the Apache Public License (version 2).[/quote]
代码很长,大家慢慢消化吧!
要跟踪代码的前提是需要有生成好的代码,所以我们需要新建一个Titanium工程,然后编译生成iPhone代码。编译后在/build/iphone/下就能够找到源代码了。
由于版本的不一样,编译出来的代码有可能也不一样,我使用的版本是1.7.1。而且,代码有可能会很长,在一些地方我会省去没有关系的代码。
[size=large][b]1、/build/iphone/main.m[/b][/size]
25:int main(int argc, char *argv[]) {
26: NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
27:
28:#ifdef __LOG__ID__
29: NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
30: NSString *documentsDirectory = [paths objectAtIndex:0];
31: NSString *logPath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%s.log",STRING(__LOG__ID__)]];
32: freopen([logPath cStringUsingEncoding:NSUTF8StringEncoding],"w+",stderr);
33: fprintf(stderr,"[INFO] Application started\n");
34:#endif
35:
36: int retVal = UIApplicationMain(argc, argv, nil, @"TiApp");
37: [pool release];
38: return retVal;
39:}
这里从代码UIApplicationMain(argc, argv, nil, @"TiApp")可以看出来我们需要进一步进入到TiApp.mm中查看代码。
TiApp是应用在启动的时候被调用的,所以继续看application:didFinishLaunchingWithOptions方法。
[size=large][b]2、/build/iphone/Classes/TiApp.mm[/b][/size]
350:- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions_
351:{
352: started = [NSDate timeIntervalSinceReferenceDate];
353: NSSetUncaughtExceptionHandler(&MyUncaughtExceptionHandler);
354:
355: // nibless window
356: window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
357:
358: [self initController];
359:
(省略)
384: if (notification!=nil)
385: {
386: [self generateNotification:notification];
387: }
388:
389: [self boot];
390:
391: return YES;
392:}
这里可以看出是通过[self boot]来启动的,所以继续看[self boot]。
[size=large][b]3、/build/iphone/Classes/TiApp.mm[/b][/size]
281:- (void)boot
282:{
283: NSLog(@"[INFO] %@/%@ (%s.1293a6d)",TI_APPLICATION_NAME,TI_APPLICATION_VERSION,TI_VERSION_STR);
284:
285: sessionId = [[TiUtils createUUID] retain];
286: TITANIUM_VERSION = [NSString stringWithCString:TI_VERSION_STR encoding:NSUTF8StringEncoding]retain];
287:
288: NSString *filePath = [[NSBundle mainBundle] pathForResource:@"debugger" ofType:@"plist"];
289: if (filePath != nil) {
290: NSMutableDictionary *params = [[NSMutableDictionary alloc] initWithContentsOfFile:filePath];
291: NSString *host = [params objectForKey:@"host"];
292: NSString *port = [params objectForKey:@"port"];
293: if (host != nil && ![host isEqual:@""] && ![host isEqual:@"__DEBUGGER_HOST__"])
294: {
295: [self setDebugMode:YES];
296: TiDebuggerStart(host,[port intValue]);
297: }
298: }
299:
300: kjsBridge = [[KrollBridge alloc] initWithHost:self];
301:
302: [kjsBridge boot:self url:nil preload:nil];
303:#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
304: if ([TiUtils isIOS4OrGreater])
305: {
306: [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
307: }
308:#endif
309:}
注意这里的代码[kjsBridge boot:self url:nil preload:nil];
kjsBridge是KrollBridge类的变量,所以继续查看KrollBridge的boot方法。
[size=large][b]4、/build/iphone/Classes/KrollBridge.mm[/b][/size]
315:- (void)boot:(id)callback url:(NSURL*)url_ preload:(NSDictionary*)preload_
316:{
317: preload = [preload_ retain];
318: [super boot:callback url:url_ preload:preload_];
319: context = [[KrollContext alloc] init];
320: context.delegate = self;
321: [context start];
322:}
继续查看[context start];。
[size=large][b]5、/build/iphone/Classes/KrollContext.mm[/b][/size]
747:-(void)start
748:{
749: if (stopped!=YES)
750: {
751: @throw [NSException exceptionWithName:@"org.test3.kroll"
752: reason:@"already started"
753: userInfo:nil];
754: }
755: stopped = NO;
756: [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
757:}
继续查看通过@selector(main)指定的方法。
[size=large][b]6、/build/iphone/Classes/KrollContext.mm[/b][/size]
942:-(void)main
943:{
944: NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
945: [[NSThread currentThread] setName:[self threadName]];
946: pthread_rwlock_rdlock(&KrollGarbageCollectionLock);
947:// context = TiGlobalContextCreateInGroup([TiApp contextGroup],NULL);
948: context = TiGlobalContextCreate(NULL);
949: TiObjectRef globalRef = TiContextGetGlobalObject(context);
950:
951:
952: // TODO: We might want to be smarter than this, and do some KVO on the delegate's
953: // 'debugMode' property or something... and start/stop the debugger as necessary.
954: if ([[self delegate] shouldDebugContext]) {
955: debugger = TiDebuggerCreate(self,globalRef);
956: }
(省略)
1042: loopCount = 0;
1043: #define GC_LOOP_COUNT 5
1044:
1045: if (delegate!=nil && [delegate respondsToSelector:@selector(didStartNewContext:)])
1046: {
1047: [delegate performSelector:@selector(didStartNewContext:) withObject:self];
1048: }
1049: pthread_rwlock_unlock(&KrollGarbageCollectionLock);
1050:
1051: BOOL exit_after_flush = NO;
继续查看[delegate performSelector:@selector(didStartNewContext:) withObject:self]。关于KrollBridge的boot的delegate、是通过以下方法的context.delegate设定的内容:
[size=large][b]7、/build/iphone/Classes/KrollBridge.mm[/b][/size]
315:- (void)boot:(id)callback url:(NSURL*)url_ preload:(NSDictionary*)preload_
316:{
317: preload = [preload_ retain];
318: [super boot:callback url:url_ preload:preload_];
319: context = [[KrollContext alloc] init];
320: context.delegate = self;
321: [context start];
322:}
self类型就是KrollBridge类,继续查看KrollBridge的didStartNewContext方法。
[size=large][b]8、/build/iphone/Classes/KrollBridge.mm[/b][/size]
523:-(void)didStartNewContext:(KrollContext*)kroll
524:{
525: // create test3 global object
526: NSString *basePath = (url==nil) ? [TiHost resourcePath] : [[[url path] stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"."];
527: _test3 = [[test3Object alloc] initWithContext:kroll host:host context:self baseURL:[NSURL fileURLWithPath:basePath]];
(省略)
563: else
564: {
565: // now load the app.js file and get started
566: NSURL *startURL = [host startURL];
567: [self injectPatches];
568: [self evalFile:[startURL absoluteString] callback:self selector:@selector(booted)];
569: }
570:}
[size=large][color=red][b]在这里读入app.js文件。[/b][/color][/size]
继续看[self evalFile:[startURL absoluteString] callback:self selector:@selector(booted)] 这段代码。
437:- (void)evalFile:(NSString*)path callback:(id)callback selector:(SEL)selector
438:{
439: [context invokeOnThread:self method:@selector(evalFileOnThread:context:) withObject:path callback:callback selector:selector];
440:}
这里@selector(evalFileOnThread:context:)指定的方法代码如下:
346:- (void)evalFileOnThread:(NSString*)path context:(KrollContext*)context_
347:{
348: NSError *error = nil;
349: TiValueRef exception = NULL;
350:
351: TiContextRef jsContext = [context_ context];
(省略)
395: const char *urlCString = [[url_ absoluteString] UTF8String];
396:
397: TiStringRef jsCode = TiStringCreateWithCFString((CFStringRef) jcode);
398: TiStringRef jsURL = TiStringCreateWithUTF8CString(urlCString);
399:
400: // validate script
401: // TODO: we do not need to do this in production app
402: if (!TiCheckScriptSyntax(jsContext,jsCode,jsURL,1,&exception))
403: {
404: id excm = [KrollObject toID:context value:exception];
405: NSLog(@"[ERROR] Syntax Error = %@",[TiUtils exceptionMessage:excm]);
406: [self scriptError:[TiUtils exceptionMessage:excm]];
407: }
408:
409: // only continue if we don't have any exceptions from above
410: if (exception == NULL)
411: {
412: if ([[self host] debugMode]) {
413: TiDebuggerBeginScript(context_,urlCString);
414: }
415:
416: TiEvalScript(jsContext, jsCode, NULL, jsURL, 1, &exception);
417:
418: if ([[self host] debugMode]) {
419: TiDebuggerEndScript(context_);
420: }
421:
422: if (exception!=NULL)
423: {
424: id excm = [KrollObject toID:context value:exception];
425: NSLog(@"[ERROR] Script Error = %@.",[TiUtils exceptionMessage:excm]);
426: [self scriptError:[TiUtils exceptionMessage:excm]];
427: }
428: else {
429: evaluationError = NO;
430: }
通过TiCheckScriptSyntax对JavaScript的语法进行验证,然后利用TiEvalScript来执行JavaScript代码。
TiEvalScript是在/build/iphone/headers/TiCore/TiBase.h中定义的。
105:/* Script Evaluation */
106:
107:/*!
108:@function TiEvalScript
109:@abstract Evaluates a string of Ti.
110:@param ctx The execution context to use.
111:@param script A TiString containing the script to evaluate.
112:@param thisObject The object to use as "this," or NULL to use the global object as "this."
113:@param sourceURL A TiString containing a URL for the script's source file. This is only used when reporting exceptions. Pass NULL if you do not care to include source file information in exceptions.
114:@param startingLineNumber An integer value specifying the script's starting line number in the file located at sourceURL. This is only used when reporting exceptions.
115:@param exception A pointer to a TiValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception.
116:@result The TiValue that results from evaluating script, or NULL if an exception is thrown.
117:*/
118:JS_EXPORT TiValueRef TiEvalScript(TiContextRef ctx, TiStringRef script, TiObjectRef thisObject, TiStringRef sourceURL, int startingLineNumber, TiValueRef* exception);
JS_EXPORT是下边这样的,通过共享Library读入的。
[size=large][b]9、/build/iphone/headers/TiCore/TiBase.h[/b][/size]
72:/* Ti symbol exports */
73:
74:#undef JS_EXPORT
75:#if defined(BUILDING_WX__)
76: #define JS_EXPORT
77:#elif defined(__GNUC__) && !defined(__CC_ARM) && !defined(__ARMCC__)
78: #define JS_EXPORT __attribute__((visibility("default")))
79:#elif defined(_WIN32_WCE)
80: #if defined(JS_BUILDING_JS)
81: #define JS_EXPORT __declspec(dllexport)
82: #elif defined(JS_IMPORT_JS)
83: #define JS_EXPORT __declspec(dllimport)
84: #else
85: #define JS_EXPORT
86: #endif
87:#elif defined(WIN32) || defined(_WIN32)
88: /*
89: * TODO: Export symbols with JS_EXPORT when using MSVC.
90: * See http://bugs.webkit.org/show_bug.cgi?id=16227
91: */
92: #if defined(BUILDING_TiCore) || defined(BUILDING_WTF)
93: #define JS_EXPORT __declspec(dllexport)
94: #else
95: #define JS_EXPORT __declspec(dllimport)
96: #endif
97:#else
98: #define JS_EXPORT
99:#endif
关于TiEvalScript的代码在/build/iphone下不存在、查找[url=https://github.com/appcelerator/titanium_mobile]https://github.com/appcelerator/titanium_mobile[/url] 之后,在
titanium_mobile/iphone/SConstruct 中有如下的说明:
[quote]10:# NOTE: this is simply a pre-built version of the source at http://github.com/appcelerator/tijscore
11:# since this is so freaking complicated to setup and build in an stable environment, and since
12:# it takes like an hour to build the library, we have, as a convenience, pre-built it from the
13:# exact same source and are providing the pre-compiled versions for i386/arm[/quote]
[url=http://github.com/appcelerator/tijscore]http://github.com/appcelerator/tijscore[/url] 里有实际的源代码。
[size=large][b]10、/TiCore/API/TiBase.cpp[/b][/size]
52:TiValueRef TiEvalScript(TiContextRef ctx, TiStringRef script, TiObjectRef thisObject, TiStringRef sourceURL, int startingLineNumber, TiValueRef* exception)
53:{
54: TiExcState* exec = toJS(ctx);
55: APIEntryShim entryShim(exec);
56:
57: TiObject* jsThisObject = toJS(thisObject);
58:
59: // evaluate sets "this" to the global object if it is NULL
60: TiGlobalObject* globalObject = exec->dynamicGlobalObject();
61: SourceCode source = makeSource(script->ustring(), sourceURL->ustring(), startingLineNumber);
62: Completion completion = evaluate(globalObject->globalExec(), globalObject->globalScopeChain(), source, jsThisObject);
63:
64: if (completion.complType() == Throw) {
65: if (exception)
66: *exception = toRef(exec, completion.value());
67: return 0;
68: }
69:
70: if (completion.value())
71: return toRef(exec, completion.value());
72:
73: // happens, for example, when the only statement is an empty (';') statement
74: return toRef(exec, jsUndefined());
75:}
在tijscore的README中,有如下的说明,可见Titanium Mobile对于JavaScript的执行使用了WebKit的KJS。
****传统上,WebKit包含一个网页引擎WebCore和一个脚本引擎JavaScriptCore,它们分别对应的是KDE的KHTML和KJS。
[quote]This is a Titanium Mobile fork of WebKit KJS. All changes are made available
under the Apache Public License (version 2).[/quote]
代码很长,大家慢慢消化吧!