[转]JavaScriptCore and iOS 7
原文:http://www.bignerdranch.com/blog/javascriptcore-and-ios-7/
As a rule, iOS programmers don’t think much about JavaScript. We spend our days swimming in C andObjective-C, while occasionally dipping our toes into the waters of C++. But JavaScript has been consistently popular for the past decade, mostly on the web—both in the browser and on the back end.
Until iOS 7, there was only one way to interact with JavaScript from an iOS app. UIWebView
exposes one method, -stringByEvaluatingJavaScriptFromString:
. A UIWebView
has its own JavaScript runtime, and you can use this simple API to evaluate arbitrary scripts, which might be calling existing functions in the HTML document that the web view is displaying. While having access to the JavaScript engine is nice, this single method API is very limiting.
Enter the JavaScriptCore framework. Its C-based API has been exposed on OS X for some time, but with iOS 7 and OS X 10.9, it has been updated and expanded with an Objective-C interface. JavaScriptCore gives developers deep access to the full JavaScript runtime from Objective-C. You can syntax-check and execute scripts, access variables, receive callbacks, and share Objective-C objects, making possible a wide range of interactions. (One caveat: on iOS, there is currently no way to access the UIWebView’s runtime, so unfortunately it’s not possible to tie into web apps at this level.)
Share and Share Alike
JavaScript runs in a virtual machine, which is represented by the JSVirtualMachine
class. It’s very lightweight; the most important thing to know about it is that you can support multithreaded JavaScript by instantiating multiple JSVirtualMachine
s. Within each JSVirtualMachine
can be an arbitrary number of JSContext
s. A JSContext
talks to a JavaScript runtime environment and provides several key capabilities, two of which are especially relevant to this quick tour: access to the global object, and the ability to execute scripts.
Every variable defined in the JavaScript global context is exposed through keyed subscript access in the JSContext
. Hence you can access the contents of JavaScript variables in much the same way you would store and retrieve values in an NSDictionary. For example:
JSContext *context = [[JSContext alloc] initWithVirtualMachine:[[JSVirtualMachine alloc] init]];
context[@"a"] = @5;
At this point, the internal global object for the JavaScript execution context has a property named a whose value is 5. (To put it another way, there is a global variable a
whose value is 5; i.e. what you would get if you typed var a = 5
.) To go the other direction, you need to know about JSValue
s. The aptly named JSValue
class is the counterpart to an arbitrary piece of JavaScript data. Any JavaScript-native datatype can be expressed by a JSValue
. To get the value of a JavaScript variable, here’s what the code looks like.
JSValue *aValue = context[@"a"];
double a = [aValue toDouble];
NSLog(@"%.0f", a);
2013-09-13 14:15:56.021 JSThing[53260:a0b] 5
The value of our local Objective-C variable a
is 5.0, as you would expect. Now let’s execute a script to do something interesting. Note that you have to go back to the context to get a new JSValue
for a
. If you try printing aValue
, you’ll notice that its value is still 5.
[context evaluateScript:@"a = 10"]; JSValue newAValue = context[@"a"]; NSLog(@"%.0f", [newAValue toDouble]); *2013-09-13 14:15:56.021 JSThing[53260:a0b] 10**
After these lines of code, the variable x
(both in our Objective-C stack context and in the JavaScript runtime) is equal to 10.0. A JSValue
, much like NSString
, is immutable, so you can’t modify it in Objective-C to cause the value of the JavaScript x
to change. Likewise, you can’t change the value of the variable a
in JavaScript and expect your JSValue
to change. Each time a change is made, it has to be copied across the boundary of the two execution environments.
Functional Execution
In order to do anything useful with your JavaScript execution environment, you’ll need to be able to actually execute JavaScript code. Unlike UIWebViews displaying web pages with scripts already defined, your newly-created JSContext
is a blank slate. Rectify that by creating a function and executing it.
[context evaluateScript:@"var square = function(x) {return xx;}"]; JSValue *squareFunction = context[@"square"]; NSLog(@"%@", squareFunction); JSValue *aSquared = [squareFunction callWithArguments:@[context[@"a"]]]; NSLog(@"a^2: %@", aSquared); JSValue *nineSquared = [squareFunction callWithArguments:@[@9]]; NSLog(@"9^2: %@", nineSquared); *2013-09-13 14:22:37.597 JSThing[53327:a0b] function (x) {return xx;}* 2013-09-13 14:17:40.581 JSThing[53282:a0b] a^2: 25 2013-09-13 14:17:40.582 JSThing[53282:a0b] 9^2: 81
The -callWithArguments
method of JSValue
takes an NSArray
of arguments and will only work if the receiver is a valid JavaScript function; if you had a syntax error in your function the resulting values from -callWithArguments
would have been undefined. Note that arguments may be passed either as NSNumber
s or JSValue
s.
Functions can work the other way, too. You can pass an Objective-C block to a JSContext
and it will then behave as a function. That way, your scripts can call back into your iOS code:
context[@"factorial"] = ^(int x) {
int factorial = 1;
for (; x > 1; x--) {
factorial *= x;
}
return factorial;
};
[context evaluateScript:@"var fiveFactorial = factorial(5);"];
JSValue *fiveFactorial = context[@"fiveFactorial"];
NSLog(@"5! = %@", fiveFactorial);
2013-09-13 14:22:37.598 JSThing[53327:a0b] 5! = 120
By providing a function in this way, you can write your JavaScript code to trigger callbacks in your iOS code. There are some gotchas—you should avoid capturing references to JSValue
or JSContext
objects from within your function blocks, since those objects retain references that are likely to cause leaks.
Object Permanence
A JSValue
wraps all manner of JavaScript values. This includes primitives and object types, along with a few convenience methods for commonly used classes such as NSArray
. Here’s the complete list, courtesy of JSValue.h
:
// Objective-C type | JavaScript type
// --------------------+---------------------
// nil | undefined
// NSNull | null
// NSString | string
// NSNumber | number, boolean
// NSDictionary | Object object
// NSArray | Array object
// NSDate | Date object
// NSBlock | Function object
// id | Wrapper object
// Class | Constructor object
So far, you’ve passed NSNumber
s and NSBlock
s back and forth. While this is interesting and useful, notice that none of the built-in bridged types is mutable. This means that there’s no way to have a variable in Objective-C code that maps to a JavaScript value such that modifying one changes the other as well.
Or is there? Take a look at the last two lines of this table. Any object or class can be bridged to JavaScript. Any attempt to pass an object to a JSContext
which is not NSNull
, NSString
,NSNumber
, NSDictionary
, NSArray
or NSDate
will result in JavaScriptCore importing the relevant class hierarchy into the JavaScript execution context and creating equivalent classes and prototypes.
You can use the JSExport
protocol to expose parts of your custom classes to JavaScript. A wrapper object will be created on the other side, which will act as a passthrough. Thus one object can be shared between both execution contexts, read or changed by either one and consistent across both.
Wrapping Up
Here’s a simple example of how to bridge your own class to JavaScript.
@protocol ThingJSExports <JSExport>
@property (nonatomic, copy) NSString *name;
@end
@interface Thing : NSObject <ThingJSExports>
@property (nonatomic, copy) NSString *name;
@property (nonatomic) NSInteger number;
@end
@implementation Thing
- (NSString *)description {
return [NSString stringWithFormat:@"%@: %d", self.name, self.number];
}
@end
By declaring that ThingJSExports
inherits from JSExport
, you mark the name property to be exposed to JavaScript. Now you can create an object that exists in both environments simultaneously. If you make changes to the Objective-C object, you will notice changes in the JavaScript value representing it. If you make changes to exposed properties on the JavaScript object, those changes will be reflected in the Objective-C object.
Thing *thing = [[Thing alloc] init];
thing.name = @"Alfred";
thing.number = 3;
context[@"thing"] = thing;
JSValue *thingValue = context[@"thing"];
Logging out the initial values produces expected results:
NSLog(@"Thing: %@", thing);
NSLog(@"Thing JSValue: %@", thingValue);
2013-09-13 14:25:48.516 JSThing[53344:a0b] Thing: Alfred: 3 ** 2013-09-13 14:25:48.517 JSThing[53344:a0b] Thing JSValue: Alfred: 3**
Now, if you change the properties of the local object, the wrapped JavaScript object changes:
thing.name = @"Betty";
thing.number = 8;
NSLog(@"Thing: %@", thing);
NSLog(@"Thing JSValue: %@", thingValue);
2013-09-13 14:25:48.517 JSThing[53344:a0b] Thing: Betty: 8 2013-09-13 14:25:48.517 JSThing[53344:a0b] Thing JSValue: Betty: 8
However, the only changes that can propagate from JavaScript to Objective-C are those to the name property.
[context evaluateScript:@"thing.name = \"Carlos\"; thing.number = 5"];
NSLog(@"Thing: %@", thing);
NSLog(@"Thing JSValue: %@", thingValue);
2013-09-13 14:25:48.518 JSThing[53344:a0b] Thing: Carlos: 8 2013-09-13 14:25:48.518 JSThing[53344:a0b] Thing JSValue: Carlos: 8
(What happened when we set thing.number
to 5 in our script? Hint: try evaluating the script"thing.number"
before and after modifying it from JavaScript and see what the resulting JSValue
s are.)
This post has just scratched the surface of JavaScriptCore. Object instantiation, script syntax checking, regular expression support, and more are available either through the Objective-C API or the lower-level C API. JavaScriptCore is part of WebKit
, which is an open-source project. If you’re curious, you can poke around in the source here.
[转]JavaScriptCore and iOS 7的更多相关文章
- [转]编译 JavaScriptCore For iOS
from: http://phoboslab.org/log/2011/06/javascriptcore-project-files-for-ios github: https://github.c ...
- 转载 iOS js oc相互调用(JavaScriptCore) --iOS调用js
iOS js oc相互调用(JavaScriptCore) 从iOS7开始 苹果公布了JavaScriptCore.framework 它使得JS与OC的交互更加方便了. 下面我们就简单了解一下这 ...
- IOS开发基础知识--碎片41
1:UIWebView加载本地的HTML NSString *path = [[NSBundle mainBundle] bundlePath]; NSURL *baseURL = [NSURL fi ...
- iOS7新JavaScriptCore框架入门介绍
前阵子,Apple正式发布了新的iOS 7系统,最大最直观的改变在于界面变得小清新范了,我也提到<iOS,你真的越来越像Android了>.不过对于移动开发者来说,除了要适应Xcode 5 ...
- iOS 7新功能例子
参考https://github.com/shu223/iOS7-Sampler Code examples for the new functions of iOS 7. Contents Dyna ...
- [转]JavaScriptCore by Example
原文:http://www.bignerdranch.com/blog/javascriptcore-example/ JavaScriptCore is not a new framework; i ...
- JavaScriptCore全面解析
本文由云+社区发表 作者:殷源,专注移动客户端开发,微软Imagine Cup中国区特等奖获得者 JavaScript越来越多地出现在我们客户端开发的视野中,从ReactNative到JSpatch, ...
- JavaScriptCore全面解析 (上篇)
收录待用,修改转载已取得腾讯云授权 作者 | 殷源 编辑 | 迷鹿 殷源,专注移动客户端开发,微软Imagine Cup中国区特等奖获得者,现就职于腾讯. JavaScript越来越多地出现在我们客户 ...
- iOS与JS开发交互总结
hybrid.jpg 前言 Web 页面中的 JS 与 iOS Native 如何交互是每个 iOS 猿必须掌握的技能.而说到 Native 与 JS 交互,就不得不提一嘴 Hybrid. Hybri ...
随机推荐
- mysql_SQL_按照日统计微博数
主要备忘: DATE_FORMAT 函数 1:微博对比图(按日统计) SELECT DATE_FORMAT(tw.article_publish_time, '%Y-%m-%d'),count(pag ...
- duplicate symbol _*** in:
duplicate symbol _kReachabilityChangedNotification in: 问题出在同一个文件被引用两次,在项目中找到引用的地方,删掉对应的引用
- c++使用stmp协议发送电子邮件(163邮箱,TTL非SSL)
0.有关TLS和SSL SSL/TLS是用来加密邮件流的. 假设不加密的话非常easy被人破解. 只是既然是要发送广告邮件,这个就不用在意了,使用没加密的即可. 另外在使用的时候,发现,qq的邮箱须要 ...
- VTune使用amplxe-cl进行Hardware Event-based Sampling Analysis 0分析
于BASH正在使用VTune进行Hardware Event-based Sampling Analysis 0分析: 结果(部分)例如以下: 版权声明:本文博客原创文章.博客,未经同意,不得转载.
- Socket 学习(三).5 UDP 的弱点
前面 讲到了,udp 传输文本的例子,发现 udp 确实 比tcp 高效一些,现在我用来传输文件,问题果然来了,结果发现 他不能一次 传输大于 64K的东西! 那么 我自然想到了 切包,多次发送,再合 ...
- IOS开发计算文本尺寸
在IOS开发中例如微博,QQ聊天界面中要显示大量的文字信息,这样需要计算出文字部分的尺寸,才能设计出合适的控件尺寸和位置.下面是IOS 7.0计算文本尺寸的方法.- (CGRect)boundingR ...
- 【甘道夫】Ubuntu群集配置 - 免费登陆
引言 这是几年前写的文章,但一直以来该问题被反复问到.所以我决定将它又一次搬上屏幕. 正文 三个节点:masternode slavenode1 slavenode2 第一步:全部节点分别生 ...
- POJ 2262 Goldbach's Conjecture(素数相关)
POJ 2262 Goldbach's Conjecture(素数相关) http://poj.org/problem?id=2262 题意: 给你一个[6,1000000]范围内的偶数,要你将它表示 ...
- Theano学习笔记(一)——代数
标量相加 import theano.tensor as T from theano import function x = T.dscalar('x') y = T.dscalar('y') z = ...
- href 做导航 特效
<div> <div> <%for (int i = 0; i < 200; i++) { %><%=i.ToString() %> <br ...