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?
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.
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.
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.
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]);
}
}
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With