[转]JavaScriptCore by Example
原文:http://www.bignerdranch.com/blog/javascriptcore-example/
JavaScriptCore is not a new framework; in fact, it's been lurking on Mac OS X ever since version 10.2. But with iOS 7 and Mac OS 10.9, Apple has introduced a native Objective-C API for JavaScriptCore. Apple does provide some documentation in the form of programmer’s comments in the header files, but otherwise this API is poorly documented. We first wrote about JavaScriptCore and iOS 7 last fall, but today, I want to demonstrate how and why one might include JavaScript in an iOS app. By the end you will be able to:
Create and call JavaScript functions from Objective-C
Catch JavaScript exceptions
Call back into Objective-C from JavaScript
Leverage the JavaScript context of a Web View
Complete code for this project is available on GitHub.
All My Contacts
Let's start by looking at an example. I have written a simple contact management app for iOS. The app comes pre-populated with the contact info from some of my dearest friends.

At this point, the app can display the contact list and supports basic editing like rearranging and deleting cells. Here's a look at the public header for the BNRContact model class:
@interface BNRContact : NSObject
@property (nonatomic, readonly) NSString *name;
@property (nonatomic, readonly) NSString *phone;
@property (nonatomic, readonly) NSString *address;
+ (instancetype)contactWithName:(NSString *)name
phone:(NSString *)phone
address:(NSString *)address;
@end
Playing with Matches
In its present state, the app trusts that phone numbers are given in a well-formed manner, but this is insufficient. Notice that the phone number for Zapp Brannigan contains a single digit. However, we want only numbers that match our chosen format. To enforce this, let's introduce a JavaScript function:
var isValidNumber = function(phone) {
var phonePattern = /^[0-9]{3}[ ][0-9]{3}[-][0-9]{4}$/;
return phone.match(phonePattern) ? true : false;
}
This JavaScript function uses a regular expression to test if the given phone number matches our chosen pattern. We will call this function to validate the phone number each time someone calls the contactWithName:phone:address: method on the BNRContact class.
+ (instancetype)contactWithName:(NSString *)name phone:(NSString *)phone address:(NSString *)address
{
if ([self isValidNumber:phone]) {
BNRContact *contact = [BNRContact new];
contact.name = name;
contact.phone = phone;
contact.address = address;
return contact;
} else {
NSLog(@"Phone number %@ doesn't match format", phone);
return nil;
}
}
+ (BOOL)isValidNumber:(NSString *)phone
{
// getting a JSContext
JSContext *context = [JSContext new];
// defining a JavaScript function
NSString *jsFunctionText =
@"var isValidNumber = function(phone) {"
" var phonePattern = /^[0-9]{3}[ ][0-9]{3}[-][0-9]{4}$/;"
" return phone.match(phonePattern) ? true : false;"
"}";
[context evaluateScript:jsFunctionText];
// calling a JavaScript function
JSValue *jsFunction = context[@"isValidNumber"];
JSValue *value = [jsFunction callWithArguments:@[ phone ]];
return [value toBool];
}
Let's take a look at the steps taken in the isValidNumber: method.
Getting a JSContext
JSContext is the main point of entry when working with the JavaScriptCore framework. The JSContext object represents the state of your JavaScript environment. You can define objects, primitives and functions within your JSContext. These entities will live on until the JSContext is released. You may specify a JSVirtualMachine when creating your JSContext[1. This is required if you wish to execute JavaScript functions in parallel, as each JSVirtualMachine runs on a single thread.], but the default virtual machine is fine for now.
Defining a JavaScript Function
We are using JSContext's evaluateScript: method to define our JavaScript function. This method takes a string representation of some JavaScript code. So our first order of business is to load our JavaScript function into a string. This can be done any number of ways[2. If you plan on writing much JavaScript code, I recommend using a JavaScript editor and loading from a file. Xcode is not a JavaScript editor.], but I have chosen to build the string inline. After this step completes, our JSContext will have a function named isValidNumber.
Calling a JavaScript Function
Next, we ask for a handle to the isValidNumber function within the JSContext. This handle is returned as a JSValue. The JSValue object provides a callWithArguments: method for directly calling a JavaScript function. Our isValidNumber function takes exactly one argument—a phone number—and returns a boolean. JavaScriptCore automatically wraps this boolean in a JSValue object. Along with boolean, many other common types (both primitive and object) are supported, including NSString, NSDate, NSDictionary, NSArray and more[3. For more information on supported types, see the programmer’s notes in the JSValue header file. ]. JavaScriptCore provides convenience methods for marshalling JavaScript types to supported Objective-C types. One example of this is the toBool method used on the last line of our isValidNumber: method.
Now whenever we attempt to add a new contact, the phone number will be validated. If it does not match our chosen format, the contact will not be created. Let's see this in action.

Zapp Brannigan did not make the list this time. His dastardly phone number was no match for our mighty isValidNumber function.
Catch Me If You Can
Before we get too far along in our JavaScript adventures, we should look at some error handling. JavaScriptCore allows one to specify an Objective-C block to be called whenever an exception occurs. In our isValidNumber: method, let's add such a block in order to catch JavaScript exceptions.
[context setExceptionHandler:^(JSContext *context, JSValue *value) {
NSLog(@"%@", value);
}];
Now whenever a JavaScript exception occurs, the exception message (the value parameter passed into the block) will be logged. The exception will give us some helpful information about what went wrong in the JavaScript code. For instance, if we forget to end a function call with a closing parenthesis, an exception will occur, and JavaScriptCore will inform us of the missing symbol. Even this trivial amount of error handling can go a long way on dark and stormy nights when nothing seems to be working.
The Wild Wild Web
A primary reason for using JavaScript in Objective-C apps is to interact with web content in UIWebView instances. Since iOS 2, the only official way to do so has been through UIWebView'sstringByEvaluatingJavaScriptFromString: method. Unfortunately, this hasn't changed with the introduction of JavaScriptCore for iOS.
A Word of Caution
Although they have given us a fantastic new toy for JavaScript, Apple seems reluctant to allow us to use it when dealing with web content from a UIWebView. As developers, we see the possibilities and want to leverage this power for our web apps. Be aware that this section shows you how to grab the JSContext from a UIWebView—something Apple probably doesn't want you to do. You have been warned.
A Little KVC Goes a Long Way
So far, we have been working with stand-alone JSContext objects that have been created on the fly. UIWebView instances have their own JSContext objects, and in order to manipulate their web content, we will need access to this JSContext. Apple has not provided us with an accessor for UIWebView's JSContext property, but fortunately key-value coding has our back. Using KVC, we can ask a UIWebView instance for its JSContext property[4. An alternate approach for retrieving the JSContext of a UIWebView is demonstrated in this GitHub project.].
This method works as of this writing[5. This works technically, but may well go against Apple's policy for submission to the App Store. I am not a lawyer or even a fungineer. I suggest you investigate potential consequences before attempting to use this method in a production app.], but may well break in the future.
3-2-1 Contact
Let's assume I have created a companion web app that mirrors the functionality of my iOS app. I want these two apps to work together so that I can keep my contacts in sync. Whenever the user presses the "add" button at the top of the contacts list, I want the iOS app to present a web view that loads the "add contact" page from my web app. Using JavaScriptCore, we will programmatically provide a new JavaScript listener for the "add contact" form's submit action. This function will call back into our Objective-C code. In this way, new contacts can be added simultaneously to the web app and iOS app.
Before the JavaScript function can call back into our Objective-C app, we must first inform JavaScriptCore of any desired functionality. This is done through the use of the JSExport protocol.
First, we will export the addContact: method on our BNRContactApp class.
@protocol BNRContactAppJS <JSExport>
- (void)addContact:(BNRContact *)contact;
@end
@interface BNRContactApp : NSObject <BNRContactAppJS>
...
@end
By declaring the addContact: method within the BNRContactAppJS protocol, this method will be visible in our JavaScript environment. All other methods or properties of the BNRConactApp class will be hidden.
Next we will export the contactWithName:phone:address: method on our BNRContact class.
@protocol BNRContactJS <JSExport>
+ (instancetype)contactWithName:(NSString *)name
phone:(NSString *)phone
address:(NSString *)address;
@end
@interface BNRContact : NSObject <BNRContactJS>
...
@end
Now we will implement the webViewDidFinishLoad: delegate method for our web view.
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
// get JSContext from UIWebView instance
JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
// enable error logging
[context setExceptionHandler:^(JSContext *context, JSValue *value) {
NSLog(@"WEB JS: %@", value);
}];
// give JS a handle to our BNRContactApp instance
context[@"myApp"] = self.app;
// register BNRContact class
context[@"BNRContact"] = [BNRContact class];
// add function for processing form submission
NSString *addContactText =
@"var contactForm = document.forms[0];"
"var addContact = function() {"
" var name = contactForm.name.value;"
" var phone = contactForm.phone.value;"
" var address = contactForm.address.value;"
" var contact = BNRContact.contactWithNamePhoneAddress(name, phone, address);"
" myApp.addContact(contact);"
"};"
"contactForm.addEventListener('submit', addContact);";
[context evaluateScript:addContactText];
}
First, we grab the JSContext from the UIWebView and enable error logging. (You will have errors. They will be hard to find. This will help.)
Then we create a JavaScript handle to our BNRContactApp instance. We will use this handle later to call the addContact: instance method. Next we register our BNRContact class with the JSContext. This will allow us to call the contactWithName:phone:address: class method.
Once all the preliminary work is done, it's time to define our JavaScript function for processing the web form. We start by creating a JavaScript variable that points to the form. We then gather the parameters from the form object and build a new BNRContact object. JavaScriptCore automatically maps the contactWithName:phone:address: Objective-C class method to a JavaScript function named contactWithNamePhoneAddress(name, phone, address). After the new contact is created, we want to add it to our BNRContactApp. The addContact: Objective-C instance method is automatically mapped to a JavaScript function named addContact(contact). The final line of JavaScript code assigns our new function as a listener for the form's submit action.
Let's see the result!


That's the End?
I have demonstrated how to call JavaScript functions from Objective-C using JSContext's evaluateScript: method and JSValue's callWithArguments: method. I showed you how to catch JavaScript exceptions (and strongly encouraged you to do so in your apps). Using KVC, we were able to retrieve the JSContext from a UIWebView instance. Finally, through the use of the JSExport protocol, we saw how to expose Objective-C methods to JavaScript.
Now it's your turn. Take what you've learned here and sprinkle some JavaScript in your next project. But keep in mind, Apple doesn't want you accessing private API properties in your App Store apps.
[转]JavaScriptCore by Example的更多相关文章
- iOS引入JavaScriptCore引擎框架(一)
JavaScriptCore引擎 我们都知道WebKit是个渲染引擎,简单来说负责页面的布局,绘制以及层的合成,但是WebKit工程中不仅仅有关于渲染相关的逻辑,也集成了默认的javascri ...
- 判断js引擎是javascriptCore或者v8
来由 纯粹的无聊,一直在搜索JavaScriptCore和SpiderMonkey的一些信息,却无意中学习了如何在ios的UIWebView中判断其js解析引擎的方法: if (window.de ...
- WKWebView与JS交互,UIWebView+JavascriptCore和JS交互
最近一直在做有关Swift和JavaScript交互的程序,所以有关UIWebView和WKWebView在使用上的差别在此总结下: UIWebView: (1)创建 var webView: UIW ...
- 说说JavaScriptCore
http://www.jianshu.com/p/1328e15416f3/comments/1724404 javascript目前看来仍是世界上最流行的语言,不管在web.服务端还是客户端都有广泛 ...
- iOS JavaScriptCore与H5交互时出现异常提示
在利用JavaScriptCore与H5交互时出现异常提示: This application is modifying the autolayout engine from a background ...
- JavaScriptCore 使用
JavaScriptCore JavaScriptCore是webkit的一个重要组成部分,主要是对JS进行解析和提供执行环境.代码是开源的,可以下下来看看(源码).iOS7后苹果在iPhone平台推 ...
- JavaScriptCore框架介绍
http://www.cocoachina.com/ios/20140409/8127.html 这个框架其实只是基于webkit中以C/C++实现的JavaScriptCore的一个包装,在旧版本i ...
- iOS中JS 与OC的交互(JavaScriptCore.framework)
iOS中实现js与oc的交互,目前网上也有不少流行的开源解决方案: 如:react native 当然一些轻量级的任务使用系统提供的UIWebView 以及JavaScriptCore.framewo ...
- iOS7新JavaScriptCore框架入门介绍
前阵子,Apple正式发布了新的iOS 7系统,最大最直观的改变在于界面变得小清新范了,我也提到<iOS,你真的越来越像Android了>.不过对于移动开发者来说,除了要适应Xcode 5 ...
- iOS7 中的JavaScriptCore简单介绍
以前写过一篇介绍如何使用第三方库在ios上进行js和oc交互调用的文章,链接如下 iOS 使用UIWebView把oc代码和javascript相关联.当时建立项目时,仍然是ios6时代,所以没有原生 ...
随机推荐
- 离github在导入dubbo工程
dubbo移动到github目前已经有一些时间,我们可能根本不习惯使用github.因此,我们仍然使用以前的版本号. 由于某些原因.阿里下载前关闭.在这里给大家提供一个私有地址: 链接:http:// ...
- DYNAMICRESOLUTION | NODYNAMICRESOLUTION
有时候开启OGG进程的时候较慢,可能是由于须要同步的表太多,OGG在开启进程之前会将须要同步的表建立一个记录而且存入到磁盘中,这样就须要耗费大量的时间.OGG同一时候也提供了DYNAMICRESOLU ...
- 【百度地图API】北京周边7日游——图标按路线轨迹行动
原文:[百度地图API]北京周边7日游--图标按路线轨迹行动 任务描述: 春节就要来啦~酸奶小妹的妈妈要从遥远的重庆,来到北京过春节呢!酸奶小妹忙着给妈妈计划,北京周边的旅游线路.计划好路线后,就开始 ...
- Swift语言指南(二)--语言基础之注释和分号
原文:Swift语言指南(二)--语言基础之注释和分号 注释 通过注释向自己的代码中注入不可执行的文本,作为你自己的笔记或提示.Swift编译器运行时会忽略注释. Swift的注释与C语言极其相似,单 ...
- [Attila GPU] ATTILA GPU Streamer Unit (D3D Input Assambler) 结构分析
http://www.opengpu.org/forum.php?mod=viewthread&tid=40&highlight=Attila 查看: 7488|回复: 26 [ ...
- Hack 语言学习/参考---1.1 What is Hack?
What is Hack?¶ Hack is a language for HHVM that interopates seamlessly with PHP. The barrier to entr ...
- svg的自述
svg可缩放矢量图形(Scalable Vector Graphics). SVG 使用 XML 格式定义图像. SVG 是使用 XML 来描述二维图形和绘图程序的语言. 什么是SVG? SVG 指可 ...
- Cocos2d-x 2.3.3版本 FlappyBird
Cocos2d-x 2.3.3版本 FlappyBird 本篇博客基于Cocos2d-x 2.3.3, 介绍怎样开发一款之前非常火的一款游戏FlappyBird.本篇博客内容大纲例如以下: 1 ...
- 使用 COM 风格的编程接口
使用COM 风格的编程接口 假设不直接使用 COM 库.不创建自己的包装.那么更可能的是使用 COM 风格的编程接口.这是由于如今很多开发商公布应用程序时.提供了首选的互操作程序集(Primary I ...
- WEB开发中常用的正则表达式集合
在计算机科学中,正则表达式用来描述或者匹配一系列符合某个句法规则的字符串的单个字符串.在WEB开发中,正则表达式通常用来检测.查找替换某些符合规则的字符串,如检测用户输入E-mai格式是否正确,采集符 ...