Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to safely read properties of a WebScriptObject?

When communicating between JavaScript in a WebView instance and a WebViewDelegate, JavaScript types and Objective-C types are converted back and forth. For instance, when calling an Objective-C function from JavaScript, a string becomes an NSString, a number becomes an NSNumber, and an Object becomes a WebScriptObject.

The others are pretty simple to deal with, but WebScriptObject seems weird.

When passing a dictionary like {"foo": 1, "bar": 2}, most of the code I see extracts the properties using valueForKey, such as in [[arg valueForKey:@"foo"] intValue] == 1

But what about if you're not sure if the property exists? What if the keys are optional? [arg valueForKey:@"baz"] throws an exception.

One thing I can do is something like

@try {
  foo = [[arg valueForKey:@"baz"] intValue];
}
@catch (NSException* e) {
  foo = 0;
}

but I've heard that exceptions in Objective-C are unsafe and should not be used for flow control.

The only other way I can think of is some variation of the method used here: http://edotprintstacktrace.blogspot.com/2011/10/sample-webscriptobject-javascript.html

In other words: 1. use evaluateWebScript to define a JavaScript function that implements Object.keys 2. call that function on your WebScriptObject 3. iterate through the returned array of keys, and only call valueForKey if we find a match.

This seems incredibly inefficient, to me. There must be a better way... is there?

like image 920
Zarel Avatar asked May 27 '12 21:05

Zarel


People also ask

How can we read the properties of JavaScript explain with example?

Using square brackets: In this, we use a square bracket to access the property of the object. It is the same as accessing the elements of an array using the square bracket. Example: Javascript.

How do I find the name of an object?

Access the name property on the object's constructor to get the class name of the object, e.g. obj.constructor.name . The constructor property returns a reference to the constructor function that created the instance object. Copied! We accessed the name property on the Object.

How do you add a property to an object?

One way is to add a property using the dot notation: obj. foo = 1; We added the foo property to the obj object above with value 1.


2 Answers

Since OS X 10.9 and iOS 7 there is a ways more easy way to do this. Apple introduced a class named JSValue wich can be used to bridge JavaScript objects to regular ObjC objects:

// WebScriptObject *options; obtained from a callback in ObjC
id objCObject = [[options JSValue] toObject];

if ([objCObject isKindOfClass:[NSArray class]]) {
    for (id object in objCObject) {
        NSLog(@"object: %@", object);
    }
}
else if ([objCObject isKindOfClass:[NSDictionary class]]) {
    for (id<NSCopying> key in [objCObj allKeys]) {
       NSLog(@"object for key %@: %@", key, [objCObject objectForKey:key]);
    }
}
like image 180
Julian F. Weinert Avatar answered Sep 27 '22 16:09

Julian F. Weinert


I think I found something that works - the trick is that you can convert WebScriptObject to JSObjectRef with -JSObject, and there's a whole bunch of C methods that work on JSObjectRefs, though the docs are a bit lacking so it's hard to figure out what to do exactly.

Here's how you can check if a property exists:

id getProperty(WebScriptObject *obj, NSString *prop) {
    JSStringRef jsProp = JSStringCreateWithCFString((__bridge CFStringRef) prop);

    if (JSObjectHasProperty(self.frame.globalContext, [obj JSObject], jsProp)) {
       return [options valueForKey:prop];
    } else {
       return nil;
    }

    JSStringRelease(jsString);
}

If you want to list all properties (whose keys aren't known in advance), you'll need to use a few more functions:

JSPropertyNameArrayRef properties =
    JSObjectCopyPropertyNames(self.frame.context, [obj JSObject]);

size_t count = JSPropertyNameArrayGetCount(properties);

for (NSInteger i = 0; i < count; i++) {
    JSStringRef property = JSPropertyNameArrayGetNameAtIndex(properties, i);
    // ... etc. as above
}
like image 34
Kuba Suder Avatar answered Sep 27 '22 16:09

Kuba Suder