You are on page 1of 4

PhoneGap The Internals 2010

Part 2. Where did that JavaScript go?!


In Part 1, we examined the internals of the JavaScript part of the framework, specifically phonegap.js. This time we're going to examine exactly what happens when the JavaScript calls the Objective-C code, how the Objective-C detects calls and then how it responds to them. Note: Just as part 1 was heavy with JavaScript and assumed a background with JavaScript; this one is heavy with objective-C and assumes the reader is comfortable with the iPhone SDK

So, where were we?


In the last part we had just discovered the protocol which PhoneGap uses to call Objective-C code. The calling mechanism was simply a URL encoded string sent via the "gap://" protocol. So lets pick up the example we left off with. gap://Notification.alert?message=Called%20By%20PhoneGap&title=Hello&buttonLabel=Goodbye So immediately we can tell that the above call is sent to Notification class (or command handler), and calls the alert method. We can also tell the three arguments which the call will pass to the method. Lets have a look for this class in the Objective-C code.

// /PhoneGapLib/Classes/Notification.h
@interface Notification : PhoneGapCommand { LoadingView* loadingView; } - (void)alert:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; - (void)activityStart:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; - (void)activityStop:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; - (void)vibrate:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; - (void)loadingStart:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; - (void)loadingStop:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; @end

Now, we can clearly see our alert method however there are 2 things to notice with all of the above methods. 1) They all have a return type of void - i.e. they return nothing. 2) They all have the same arguments an NSMutableArray and an NSMutableDictionary. When thinking back to the JavaScript code we examined in part 1 it should be obvious why they all take the same parameters; the JavaScript part of the framework converts the arguments to a NSMutableArray prior to handing control over to Objective-C. Plus call-back functions and similar options are specified in an object, which is passed on to these methods as an NSMutableDictionary. // /PhoneGapLib/Classes/Notification.m
- (void)alert:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options { NSString* message = [arguments objectAtIndex:0]; NSString* title = [options objectForKey:@"title"]; NSString* button = [options objectForKey:@"buttonLabel"]; if (!title) title = @"Alert"; if (!button) button = @"OK"; UIAlertView *openURLAlert = [[UIAlertView alloc] initWithTitle:title message:message delegate:nil cancelButtonTitle:button otherButtonTitles:nil]; [openURLAlert show]; [openURLAlert release] }

To anyone familiar with the Objective-C language, or the iPhone SDK this should be a pretty easy method to understand. It's a simple wrapper for the UIAlertView class. It offers some basic input checking, takes the parameters cleanly from the NSMutableArray, which it then accepts as an argument. Perhaps the only other thing to note from the code I've shown you so far, is that the Notification class inherited directly from a class named PhoneGapCommand- so lets have a little peek at that, and see exactly what it implements and how important that is. So once again, we'll have a look at the header file and see what's declared...

Fergus Morrow (http://www.fergus-morrow.com)

PhoneGap The Internals 2010


// /PhoneGapLib/Classes/PhoneGapCommand.h
@interface PhoneGapCommand : NSObject { UIWebView* webView; NSDictionary* settings; } @property (nonatomic, retain) UIWebView *webView; @property (nonatomic, retain) NSDictionary *settings; -(PhoneGapCommand*) initWithWebView:(UIWebView*)theWebView settings:(NSDictionary*)classSettings; -(PhoneGapCommand*) initWithWebView:(UIWebView*)theWebView; -(PhoneGapDelegate*) appDelegate; -(UIViewController*) appViewController; - (void) writeJavascript:(NSString*)JavaScript; - (void) clearCaches; @end

Right, perhaps the most interesting method that pops out to me writeJavascript- it's also worth noting the two initialization methods both take a UIWebView as a parameter. It's also probable looking at the code, that the appDelegate method just returns an instance of the Application's Delegate (PhoneGapDelegate), and appViewController, similarly, just returns an instance to the main UIViewController of the application. Lets have a closer inspection of these methods, it's worth remembering exactly what we inherit from PhoneGapCommand as we will be implementing our own PhoneGap Commands in a later part of this series. So here are the most interesting parts of the implementation file: // /PhoneGapLib/Classes/PhoneGapCommand.m
-(PhoneGapCommand*) initWithWebView:(UIWebView*)theWebView settings:(NSDictionary*)classSettings { self = [self initWithWebView:theWebView]; if (self) [self setSettings:classSettings]; return self; } -(PhoneGapCommand*) initWithWebView:(UIWebView*)theWebView { self = [super init]; if (self) [self setWebView:theWebView]; return self; } -(void) writeJavascript:(NSString*)JavaScript { [webView stringByEvaluatingJavaScriptFromString:javascript]; }

Note: The clean commenting/doxygen style that we noted in the JavaScript file isn't really used in this part of the code. Most likely as it's so simplistic and well formed, there's no real need for excessive comments. InitWithWebView allows the PhoneGapCommand to take a UIWebView and set it to the active view, you can also provide an NSDictionary of settings. However, the interesting method in my opinion is the inclusion of writeJavascriptstringByEvaluatingJavaScriptFromString is a method of the UIWebView; it allows you to inject JavaScript into a UIWebView. However, one of the limitations of this function is that the JavaScript cannot execute for more than 10 seconds, otherwise it's stopped by the SDK. This is documented in the iPhone OS Reference Library and the reason for this behaviour is because it can cause the application to hang until finished. If you're following along with this document and examining the source code of the framework, it may be worth your while to investigate PhoneGapDelegate.m; this is one of the goldmines of useful methods and can offer you quite a lot if you're just trying to get your head around how the framework is designed. However, the amount of interesting code in there is beyond the scope of this article however; on the next page youll see a selection of methods that I think are quite interesting.

Fergus Morrow (http://www.fergus-morrow.com)

PhoneGap The Internals 2010


// /PhoneGapLib/Classes/PhoneGapDelegate.m
+(NSString*) wwwFolderName;//Returns the name of www folder; useful if you're changing directories +(NSString*) phoneGapVersion;//Returns the version of PhoneGap -(id)getCommandInstance:(NSString*)className//Returns an instance of the PhoneGapCommand object, based on className (very nicely implemented) -(void)applicationDidFinishLaunching//As per the iPhone-SDK; this runs immediately after the application has loaded this kick starts a lot of the really neat features included in the framework! -(NSDictionary*) deviceProperties -(void) javascriptAlert:(NSString*)text //Returns an NS Dictionary of properties of the device //A simple method to run a JavaScript alert

If you're really determined to work out just how PhoneGap processes the gap://... URL then these functions below are definitely for you! If you're not interested I'm going to summarise how they work anyway, as I believe this is a very important part of the framework and shouldn't be neglected!
-(BOOL)webView:(UIWebView *)theWebView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType: (UIWebViewNavigationType)navigationType -(BOOL)execute:(InvokedUrlCommand*)command

Methods you really ought to understand


-(BOOL)webView:(UIWebView *)theWebView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType )navigationType

The first thing this method does is check the URL of the NSURLRequest object passed to it as its second parameter. If it has gap:// - then it injects the JavaScript statement PhoneGap.queue.ready = true; into the current UIWebView. It then calls the execute method on the command that has been passed to it. (It actually generates an InvokedUrlCommand object and passes that to execute but the effect is the same) However, if the URL is a local file URL it loads it internally; i.e. in the current UIWebView. Failing that, then its obviously an external request so it loads it in Safari automatically; or a native app (such as Messaging, Mail or Phone) if the protocol is tel, sms or mailto. The above may be worth noting PhoneGap is NOT meant to run external websites; and any design that requires that should be revised! By default PhoneGap will just pass all external requests straight to Safari.
-(BOOL)execute:(InvokedUrlCommand*)command

You should recognise this method as it's called from the method explained above. The first thing this method does is check that there is a valid member className or methodName in the InvokedUrlCommand object passed to it. If there isn't, then this method simply returns NO. Next up it uses the -(id)getCommandInstance:(NSString*)className method to determine which type of PhoneGapCommand the InvokedUrlCommand represents. Then it extracts the methodName from the InvokedUrlCommand. Once the invoked command has successfully run, it returns YES. Rather than trusting my explanation of the above implementations dive in and read the code yourself! I cannot stress how much you will learn if you study the code properly! It teaches you quite a bit about the framework implementation as a whole. My aim in this document is not to go through the code line-by-line and teach you every method; instead its to give you a primer in understanding the internals as well as learning where to look for specific functionality within the framework.

Fergus Morrow (http://www.fergus-morrow.com)

PhoneGap The Internals 2010


So lets recap!
We have learnt in this section that when you invoke a new URL in a PhoneGap application, its passed down to the webView() function in the Objective-C part of the framework; where the following occurs: - (BOOL)webView

Is the URL passed as "gap://" for protocol?


Is the URL passed as a local file? (file://) Is the URL a remote (http://) url?

Parse using (BOOL)execute() Load URL in the UIWebView Load in Safari

Is the URL tel:, sms:, mailto: etc?

Load native app

- (BOOL)execute: Check there is both a valid CommandHandler and Method supplied. Look up the specified PhoneGapCommand from the Invoked URL. Try and run the selected methodName in the CmmandHandler.

If any of those conditions are false, return NO

Return YES.

That's the general procedure that happens when you call a new URL from your PhoneGap application. This is the basis of the whole framework. The rest of the implementation is primarily sub-classes of PhoneGapCommand implementing the actual functionality that you call. However, without the above two methods the whole framework would be useless. In Part 3 we're going to stop looking at the internals for a while; just to see exactly how we can put this together and call an Objective-C function from a web- page from the eyes of a web developer.

Fergus Morrow (http://www.fergus-morrow.com)

You might also like