How to read WebScriptObject properties safely?

When communicating between JavaScript in an instance of WebView and WebViewDelegate JavaScript types and Objective-C types are converted back and forth. For example, when you call the Objective-C function from JavaScript, the string becomes NSString , the number becomes NSNumber , and the object becomes WebScriptObject .

The rest is pretty easy to handle, but WebScriptObject seems weird.

When going through a dictionary like {"foo": 1, "bar": 2} most of the code I see retrieves properties using valueForKey , for example, in [[arg valueForKey:@"foo"] intValue] == 1

But what about if you are 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 heard that exceptions from Objective-C are unsafe and should not be used to control flow.

The only other way I can think of is with a few variations 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 this function on your WebScriptObject 3. iterate over the returned array of keys and call only valueForKey if we find a match.

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

+7
source share
2 answers

Since OS X 10.9 and iOS 7 there are ways of an easier way to do this. Apple introduced a class called JSValue that can be used to combine JavaScript objects into 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]); } } 
+4
source

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

Here 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 the properties (whose keys are not known in advance), you will 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 } 
+3
source

All Articles