UIWebView的JavaScript失去对iOS的JSContext命名空间(对象)的引用

我一直在研究概念validation应用程序,该应用程序利用WebKit JavaScriptCore框架利用Objective C(iOS 7)和JavaScript之间的双向通信。 我终于能够按预期工作,但遇到了UIWebView失去了我通过JSContext创build的iOS对象的引用的情况。

该应用程序有点复杂,这里是基本的:

  • 我正在iOS设备上运行一个Web服务器(CocoaHTTPServer)
  • UIWebView最初加载远程URL,稍后redirect到localhost作为应用程序stream的一部分(认为OAuth)
  • 应用程序托pipe的HTML页面(在本地主机上)具有应与我的iOS代码交谈的JavaScript

这是iOS端,我的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 

和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); } 

这里是(为了这个问题简化)HTML / JS方面:

 <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> 

现在,在几乎所有的情况下,这个工程很漂亮,我看到ios is: object在UIWebView和Xcode的控制台中的对象。 很酷。 但在一个特定的情况下,100%的时间,这在UIWebView的一定数量的redirect失败后,一旦上面的页面最终加载它说:

ios是:undefined

…其余的JS逻辑退出,因为在setContent函数中对ios.jsLog的后续调用会导致未定义的对象exception。

所以最后我的问题是: 什么可能会导致JSContext丢失 ? 我通过JavaScriptCore的.h文件中的“documentation”来挖掘,发现这个应该发生的唯一方法是,如果没有更强的JSContext引用,但在我的情况下,我有我自己的一个,所以,不看起来不错。

我唯一的另一个假设是,它与我获得JSContext参考的方式有关:

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

我知道这可能不是苹果公司正式支持的,尽pipe我确实发现至less有一个这样的人说他有一个苹果认可的应用程序使用了这种方法。

编辑

我应该提到,我实现了UIWebViewDelegate以便在UIWebView中的每个redirect之后检查JSContext:

 -(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');"]; } 

这在所有情况下都能正常工作,即使当我的网页最终加载并报告ios is: undefined上面的方法同时写入Can see JS from obj c到Xcode控制台。 这似乎表明,JSContext仍然有效,并且由于某种原因,它从JS中不再可见。


对于这个非常冗长的问题抱歉,这里有很less的文件,我认为我可以提供的信息越多越好。

页面加载可以使WebView(和包装WebView的UIWebView)获得新的JSContext。

如果这是我们正在讨论的MacOS,那么正如2013年WWDC在Apple开发人员networking( https://developer.apple.com/videos/wwdc/2013)上的“将JavaScript集成到原生应用程序”会话中的WebView部分所示/?id = 615 ),则需要为框架加载实现一个委托,并在您的webViewselect器实现中初始化您的JSContextvariables:didCreateJavaScriptContext:forFrame:

在IOS的情况下,你需要在webViewDidFinishLoad中做到这一点:

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

以前的JSContext仍然可以用于Objective-C,因为你保留了强大的引用。

检查这个UIWebView JSContext

关键点是一旦JSContext发生变化就注册一个javascript对象。 我使用runloop观察器来检查是否有任何networking操作完成,一旦完成,我将得到更改的JSContext,并注册我想要的任何对象。

我没有尝试,如果这项工作的iframe,如果你必须在iframe中注册一些对象,试试这个

 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); }; }];