[转]UIWebView的Javascript运行时对象
An alternative, that may get you rejected from the app store, is to use WebScriptObject. These APIs are public on OSX but are not on iOS. You need to define interfaces to the internal classes. @interface WebScriptObject: NSObject
@end @interface WebView
- (WebScriptObject *)windowScriptObject;
@end @interface UIWebDocumentView: UIView
- (WebView *)webView;
@end
You need to define your object that's going to serve as your WebScriptObject @interface WebScriptBridge: NSObject
- (void)someEvent: (uint64_t)foo :(NSString *)bar;
- (void)testfoo;
+ (BOOL)isKeyExcludedFromWebScript:(const char *)name;
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector;
+ (WebScriptBridge*)getWebScriptBridge;
@end static WebScriptBridge *gWebScriptBridge = nil; @implementation WebScriptBridge
- (void)someEvent: (uint64_t)foo :(NSString *)bar
{
NSLog(bar);
} -(void)testfoo {
NSLog(@"testfoo!");
} + (BOOL)isKeyExcludedFromWebScript:(const char *)name;
{
return NO;
} + (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector;
{
return NO;
} + (NSString *)webScriptNameForSelector:(SEL)sel
{
// Naming rules can be found at: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/WebKit/Protocols/WebScripting_Protocol/Reference/Reference.html
if (sel == @selector(testfoo)) return @"testfoo";
if (sel == @selector(someEvent::)) return @"someEvent"; return nil;
}
+ (WebScriptBridge*)getWebScriptBridge {
if (gWebScriptBridge == nil)
gWebScriptBridge = [WebScriptBridge new]; return gWebScriptBridge;
}
@end
Now set that an instance to your UIWebView if ([uiWebView.subviews count] > ) {
UIView *scrollView = uiWebView.subviews[]; for (UIView *childView in scrollView.subviews) {
if ([childView isKindOfClass:[UIWebDocumentView class]]) {
UIWebDocumentView *documentView = (UIWebDocumentView *)childView;
WebScriptObject *wso = documentView.webView.windowScriptObject; [wso setValue:[WebScriptBridge getWebScriptBridge] forKey:@"yourBridge"];
}
}
}
Now inside of your javascript you can call: yourBridge.someEvent(, "hello");
yourBridge.testfoo();
=====================================================
As a rule, iOS programmers don’t think much about JavaScript. We spend our days swimming inC and Objective-C, while occasionally dipping our toes into the waters of C++. ButJavaScript 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. UIWebViewexposes 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 JSVirtualMachines. Within each JSVirtualMachine can be an arbitrary number of JSContexts. 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 JSValues. 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 newJSValue 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 x*x;}"];
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 x*x;}
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 NSNumbers or JSValues.
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 orJSContext 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 NSNumbers and NSBlocks 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 resultingJSValues 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.
=====================================================
[转]UIWebView的Javascript运行时对象的更多相关文章
- 教你如何检查一个函数是否为JavaScript运行时环境内建函数
在开发过程中,对于某些API在现有的JavaScript运行时环境不支持的时候,我们大都会采用加入polyfill来解决这个问题.但有些时候我们可能需要知道现在某个API到底是否为运行时环境所原生支持 ...
- JAVA调用系统命令或可执行程序--返回一个Runtime运行时对象,然后启动另外一个进程来执行命令
通过 java.lang.Runtime 类可以方便的调用操作系统命令,或者一个可执行程序,下面的小例子我在windows和linux分别测试过,都通过.基本原理是,首先通过 Runtime.getR ...
- 【基本功】Java动态追踪技术探究 不重启JVM,替换掉已经加载的类?不重启JVM,获知运行时对象的属性
https://mp.weixin.qq.com/s/_hSaI5yMvPTWxvFgl-UItA 小结: 1.根据Java的类加载机制,在同一个ClassLoader中,类是不允许重复的: 2.单例 ...
- [SQL Server]无法创建 SSIS 运行时对象,请验证 DTS.dll 是否可用及是否已注册
很大可能是SQL Server Management Studio(SSMS)版本与当前操作系统不兼容造成的,与数据库本身没有关系,这种情况基本无解,不过可以使用其他机器连本机数据库导入导出数据. 今 ...
- 深入探索.NET框架内部了解CLR如何创建运行时对象
原文地址:http://msdn.microsoft.com/en-us/magazine/cc163791.aspx 原文发布日期: 9/19/2005 原文已经被 Microsoft 删除了,收集 ...
- 深入探索.NET内部了解CLR如何创建运行时对象
前言 SystemDomain, SharedDomain, and DefaultDomain. 对象布局和内存细节. 方法表布局. 方法分派(Method dispatching). 因为公共语言 ...
- QTP测试.NET程序的时候,ComboBox下拉框控件选择后,运行时对象不可见解决方案
解决方法: 录制时,选择下拉框数据的时候,不要鼠标单击选择,而是要用ENTER(回车键)来选择,才能完成选择,这样录制就OK了.
- IOS中UIWebView执行javaScript脚本时注意点
1.webView之所以能够滚动,因为它内部有一个UIScrollView子控件 2.移除webView顶部和底部灰色的一层view * 遍历webView中scrollView内部的所有子控件 * ...
- JavaScript是如何工作的:引擎,运行时和调用堆栈的概述!
摘要: 理解JS执行原理. 原文:JavaScript是如何工作的:引擎,运行时和调用堆栈的概述! 作者:前端小智 Fundebug经授权转载,版权归原作者所有. 本文是旨在深入研究JavaScrip ...
随机推荐
- 杭电1162Eddy's picture
Eddy's picture Time Limit : 2000/1000ms (Java/Other) Memory Limit : 65536/32768K (Java/Other) Tota ...
- java_JFrame_demo
不要见笑,cs基本入行很少做 留个demo备忘 /* * Copyright (c) 2014-2024 . All Rights Reserved. * * This software is the ...
- 前端学习笔记(zepto或jquery)——对li标签的相关操作(五)
对li标签的相关操作——has与find的差异性 demo代码: <ul> <li><p>1</p></li> <li>2< ...
- Unity3D音频播放器 动态装载组件
大多数在线Unity有关如何只教程Unity在播放音乐.之后如何通过拖动它们无法继续添加音频文件 但有时在游戏中的对象要玩几个声音.这时候我们就需要使用代码控制,拖动推教程AudioClip颂值的方法 ...
- Oracle 11g password过期被锁定报道 ORA-28000 the account is locked
一.触发这个错误的原因及相关因素 是因为oracle11g中默认在default概要文件里设置了"PASSWORD_LIFE_TIME=180天"所导致.在Oracle 11g中是 ...
- applet授权数字签名
一.压缩你的class类文件为jar包 1.如果你的须要压缩的类文件存在的包为:cn.mbq.test1和cn.mbq.test2 2.进入你的classes文件夹,在DOS窗体中运行命令:jar c ...
- Cassandra C++/NodeJs开发环境
工作的需要,开始更多地倾向于去中心化的结构,目前看来Cassandra算是去中心化DB中性能/管理最热门的选择,崇尚其P2P的理念. 自身原因对JAVA不擅长(周围写C的好少),还是更热衷于C++/J ...
- 我所理解的Spring AOP的基本概念
Spring AOP中的概念晦涩难懂,读官方文档更是像读天书,看了非常多样例后,写一些自己理解的一些spring的概念.要理解面向切面编程,要首先理解代理模式和动态代理模式. 如果一个OA系统中的一个 ...
- C编程的指针涛 ---第九笔记
//这里说的是一个指针,指向算法的应用 //直接排序 //每个排序算法是指针指向的每个元件的特性的方便的交流 //这里的基本思想是,处理的记录的排序n - 1第二选择. //第i次操作选择i大(小)的 ...
- lucent检索技术之创建索引:使用POI读取txt/word/excel/ppt/pdf内容
在使用lucent检索文档时,必须先为各文档创建索引.索引的创建即读出文档信息(如文档名称.上传时间.文档内容等),然后再经过分词建索引写入到索引文件里.这里主要是总结下读取各类文档内容这一步. 一. ...