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

Mad*_*aks 18 javascript objective-c uiwebview ios javascriptcore

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

该应用程序有点复杂,以下是基础知识:

  • 我正在iOS设备上运行Web服务器(CocoaHTTPServer)
  • UIWebView最初加载远程URL,稍后重定向回localhost作为应用程序流的一部分(想想OAuth)
  • 应用程序托管的HTML页面(在localhost上)具有应该与我的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
Run Code Online (Sandbox Code Playgroud)

以及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);
}
Run Code Online (Sandbox Code Playgroud)

这是(为了这个问题而简化)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>
Run Code Online (Sandbox Code Playgroud)

现在,在几乎所有情况下,这ios is: object都很好用,我在UIWebView和Xcode的控制台中都看到了.很酷.但是在一个特定的场景中,100%的时间,在UIWebView中的一定数量的重定向后失败,并且一旦上面的页面最终加载它就说:

ios是:undefined

...和JS逻辑的其余部分退出,因为后续调用ios.jsLogsetContent的一个未定义的对象除外功能的结果.

最后我的问题是:什么可能/可能导致JSContext丢失?我挖掘了JavaScriptCore的.h文件中的"文档",发现这应该发生的唯一方法是,如果没有更多的strong引用JSContext,但在我的情况下,我有一个我自己的,所以这不似乎是对的.

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

 self.js = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
Run Code Online (Sandbox Code Playgroud)

我知道这可能不会得到Apple的正式支持,尽管我确实发现至少有一个SO'er表示他有一个苹果认可的应用程序使用了这种方法.

编辑

我应该提一下,我实现UIWebViewDelegate了在UIWebView中的每次重定向之后检查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');"];
}
Run Code Online (Sandbox Code Playgroud)

这适用于所有情况,即使我的网页最终加载并报告ios is: undefined上述方法同时写入Can see JS from obj cXcode控制台.这似乎表明JSContext仍然有效,并且由于某种原因,它在JS中根本不再可见.


对这个非常冗长的问题道歉,关于这方面的文档很少,我认为我能提供的信息越多越好.

Mik*_*ike 17

页面加载可能导致WebView(以及包装WebView的UIWebView)获取新的JSContext.

如果这是我们讨论的MacOS,那么就像2013年WWDC中关于WebView的部分所示,在Apple的开发者网络上将"将JavaScript集成到本机应用程序"会话(https://developer.apple.com/videos/wwdc/2013 /?id = 615),您需要为框架加载实现委托,并在webView的选择器实现中初始化JSContext变量: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;
}
Run Code Online (Sandbox Code Playgroud)

之前的JSContext仍然可用于Objective-C,因为您已经保留了对它的强引用.

  • 是.窗口对象在页面加载时是新的,DOM对象树也是如此.我不清楚在页面加载时JSContext正在发生什么缓存/重用策略(我希望由于性能原因会发生某些事情),但很明显Apple希望你计划更换它/新的在页面加载上创建的一个创建.如果Apple记录了一个获取UIWebView的JSContext的正式方法,事情就会简单得多. (3认同)