javascript ios objective-c uiwebview javascriptcore

UIWebView JavaScript pierde la referencia al espacio de nombres(objeto) JSContext de iOS



objective-c javascriptcore (2)

He estado trabajando en una aplicación de prueba de concepto que aprovecha la comunicación bidireccional entre Objective C (iOS 7) y JavaScript utilizando el marco de WebKit JavaScriptCore . Finalmente pude hacerlo funcionar como se esperaba, pero me he encontrado con una situación en la que UIWebView pierde su referencia al objeto iOS que he creado a través de JSContext.

La aplicación es un poco compleja, aquí están los conceptos básicos:

  • Estoy ejecutando un servidor web en el dispositivo iOS (CocoaHTTPServer)
  • El UIWebView carga inicialmente una URL remota y luego se redirige a localhost como parte del flujo de la aplicación (piense en OAuth)
  • La página HTML que aloja la aplicación (en localhost) tiene el JavaScript que debería estar hablando con mi código iOS

Aquí está el lado de iOS, mi ViewController .h :

#import <UIKit/UIKit.h> #import <JavaScriptCore/JavaScriptCore.h> // These methods will be exposed to JS @protocol DemoJSExports <JSExport> -(void)jsLog:(NSString*)msg; @end @interface Demo : UIViewController <UserInfoJSExports, UIWebViewDelegate> @property (nonatomic, readwrite, strong) JSContext *js; @property (strong, nonatomic) IBOutlet UIWebView *webView; @end

Y las partes pertinentes del ViewController .m :

-(void)viewDidLoad { [super viewDidLoad]; // Retrieve and initialize our JS context NSLog(@"Initializing JavaScript context"); self.js = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; // Provide an object for JS to access our exported methods by self.js[@"ios"] = self; // Additional UIWebView setup done here... } // Allow JavaScript to log to the Xcode console -(void)jsLog(str) { NSLog(@"JavaScript: %@", str); }

Aquí está el lado HTML / JS (simplificado por el bien de esta pregunta):

<html> <head> <title>Demo</title> <script type="text/javascript"> function setContent(c, noLog){ with(document){ open(); write(''<p>'' + c + ''</p>''); close(); } // Write content to Xcode console noLog || ios.jsLog(c); } </script> </head> <body onload="javascript:setContent(''ios is: '' + typeof ios)"> </body> </html>

Ahora, en casi todos los casos esto funciona a la perfección, veo que ios is: object tanto en UIWebView como en la consola de Xcode. Muy genial. Pero en un escenario particular, el 100% del tiempo, esto falla después de un cierto número de redireccionamientos en el UIWebView, y una vez que se carga la página anterior, dice:

ios es: indefinido

... y el resto de la lógica de JS se cierra porque la llamada posterior a ios.jsLog en la función setContent da como resultado una excepción de objeto no definido.

Entonces, finalmente, mi pregunta: ¿qué podría / puede causar que se pierda un JSContext ? Revisé la "documentación" en los archivos .h de JavaScriptCore y descubrí que la única forma en que esto debería suceder es si no hay más referencias strong al JSContext , pero en mi caso tengo una propia, así que no tengo No parece correcto.

Mi única otra hipótesis es que tiene que ver con la forma en que estoy adquiriendo la referencia JSContext :

self.js = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

Soy consciente de que es posible que Apple no lo soporte oficialmente, aunque encontré al menos un SO''er que dijo que tenía una aplicación aprobada por Apple que usaba ese mismo método.

EDITAR

Debería mencionar que implementé UIWebViewDelegate para verificar el JSContext después de cada redireccionamiento en el UIWebView:

-(void)webViewDidFinishLoad:(UIWebView *)view{ // Write to Xcode console via our JSContent - is it still valid? [self.js evaluateScript:@"ios.jsLog(''Can see JS from obj c'');"]; }

Esto funciona en todos los casos, incluso cuando mi página web finalmente se carga e informa que ios is: undefined el método anterior escribe simultáneamente Can see JS from obj c a la consola Xcode. Esto parece indicar que el JSContext sigue siendo válido, y que por alguna razón simplemente ya no es visible desde JS.

Disculpas por la larga pregunta, hay tan poca documentación sobre esto por ahí que me di cuenta de que cuanto más información pudiera proporcionar, mejor.


Compruebe este UIWebView JSContext

El punto clave es registrar un objeto javascript una vez que JSContext haya cambiado. Utilizo un observador de runloop para verificar si hay alguna operación de red finalizada, una vez que finalice, obtendré el JSContext modificado y registraré cualquier objeto que desee.

No intenté si esto funciona para iframe, si tienes que registrar algunos objetos en iframe, intenta esto

NSArray *frames = [_web valueForKeyPath:@"documentView.webView.mainFrame.childFrames"]; [frames enumerateObjectsUsingBlock:^(id frame, NSUInteger idx, BOOL *stop) { JSContext *context = [frame valueForKeyPath:@"javaScriptContext"]; context[@"Window"][@"prototype"][@"alert"] = ^(NSString *message) { NSLog(@"%@", message); }; }];


La carga de la página puede hacer que WebView (y UIWebView que envuelve a WebView) obtengan un nuevo JSContext.

Si hablamos de MacOS, entonces, como se muestra en la sección sobre WebView en la introducción de la WWDC 2013 "Integración de JavaScript en aplicaciones nativas" en la red de desarrolladores de Apple ( https://developer.apple.com/videos/wwdc/2013/?id=615 ), necesitaría implementar un delegado para la carga de cuadros e inicializar sus variables JSContext en su implementación del selector para webView: didCreateJavaScriptContext: forFrame:

En el caso de IOS, debe hacer esto en webViewDidFinishLoad:

-(void)webViewDidFinishLoad:(UIWebView *)view{ self.js = [view valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; // Undocumented access to UIWebView''s JSContext self.js[@"ios"] = self; }

El JSContext anterior todavía está disponible para Objective-C, ya que ha mantenido una fuerte referencia a él.