博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[转]UIWebView的Javascript运行时对象
阅读量:5093 次
发布时间:2019-06-13

本文共 12385 字,大约阅读时间需要 41 分钟。

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;@endYou 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;@endstatic 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;}@endNow set that an instance to your UIWebViewif ([uiWebView.subviews count] > 0) {    UIView *scrollView = uiWebView.subviews[0];    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(100, "hello");yourBridge.testfoo();

 

=====================================================

As a rule, iOS programmers don’t think much about JavaScript. We spend our days swimming in and , while occasionally dipping our toes into the waters of . But 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  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

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 NSNullNSString,NSNumberNSDictionaryNSArray 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 
@property (nonatomic, copy) NSString *name; @end @interface Thing : NSObject
@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 .

=====================================================

 

转载于:https://www.cnblogs.com/Proteas/p/3469220.html

你可能感兴趣的文章
Python内置函数(29)——help
查看>>
大数据学习系列(8)-- WordCount+Block+Split+Shuffle+Map+Reduce技术详解
查看>>
Mysql性能调优
查看>>
getElement的几中属性介绍
查看>>
设计器 和后台代码的转换 快捷键
查看>>
STL容器之vector
查看>>
数据中心虚拟化技术
查看>>
复习文件操作
查看>>
SQL Server 使用作业设置定时任务之一(转载)
查看>>
第二阶段冲刺-01
查看>>
BZOJ1045 HAOI2008 糖果传递
查看>>
JavaScript 克隆数组
查看>>
eggs
查看>>
python3 生成器与迭代器
查看>>
《Genesis-3D开源游戏引擎完整实例教程-跑酷游戏篇03:暂停游戏》
查看>>
CPU,寄存器,一缓二缓.... RAM ROM 外部存储器等简介
查看>>
git .gitignore 文件不起作用
查看>>
Alan Turing的纪录片观后感
查看>>
IOS--沙盒机制
查看>>
sqlite的坑
查看>>