How to safely read properties of a WebScriptObject?
javascript, objective-c, webkit, webview
Solution
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]);
}
}
Problem
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?