简体   繁体   English

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

[英]UIWebView JavaScript losing reference to iOS JSContext namespace (object)

I've been working on a proof of concept app that leverages two-way communication between Objective C (iOS 7) and JavaScript using the WebKit JavaScriptCore framework. 我一直在研究一个概念验证应用程序,它利用WebKit JavaScriptCore框架利用Objective C(iOS 7)和JavaScript之间的双向通信。 I was finally able to get it working as expected, but have run into a situation where the UIWebView loses its reference to the iOS object that I've created via JSContext. 我终于能够按预期工作了,但是遇到了UIWebView失去对我通过JSContext创建的iOS对象的引用的情况。

The app is a bit complex, here are the basics: 该应用程序有点复杂,以下是基础知识:

  • I'm running a web server on the iOS device (CocoaHTTPServer) 我正在iOS设备上运行Web服务器(CocoaHTTPServer)
  • The UIWebView initially loads a remote URL, and is later redirected back to localhost as part of the app flow (think OAuth) UIWebView最初加载远程URL,稍后作为应用程序流的一部分重定向回localhost (想想OAuth)
  • The HTML page that the app hosts (at localhost) has the JavaScript that should be talking to my iOS code 应用程序托管的HTML页面(在localhost上)具有应该与我的iOS代码通信的JavaScript

Here's the iOS side, my ViewController's .h : 这是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

And the pertinent parts of the ViewController's .m : 以及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);
}

Here is the (simplified for the sake of this question) HTML/JS side: 这是(为了这个问题而简化)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>

Now, in almost all cases this works beautifully, I see ios is: object both in the UIWebView and in Xcode's console. 现在,在几乎所有情况下这都很漂亮,我看到ios is: object UIWebView和Xcode控制台中的对象。 Very cool. 很酷。 But in one particular scenario, 100% of the time, this fails after a certain number of redirects in the UIWebView, and once the above page finally loads it says: 但是在一个特定的场景中,100%的时间,在UIWebView中的一定数量的重定向后失败,并且一旦上面的页面最终加载它,它说:

ios is: undefined ios是:undefined

...and the rest of the JS logic quits because the subsequent call to ios.jsLog in the setContent function results in an undefined object exception. ...以及JS逻辑的其余部分退出,因为在setContent函数中对ios.jsLog的后续调用会导致未定义的对象异常。

So finally my question: what could/can cause a JSContext to be lost ? 最后我的问题是: 什么可能/可能导致JSContext丢失 I dug through the "documentation" in the JavaScriptCore's .h files and found that the only way this is supposed to happen is if there are no more strong references to the JSContext , but in my case I have one of my own, so that doesn't seem right. 我穿过JavaScriptCore的.h文件的“文件”挖,发现这是应该发生的唯一途径是,如果没有更strong的引用JSContext ,但对我来说,我有我自己的一个,所以没有按好像没错。

My only other hypothesis is that it has to do with the way in which I'm acquiring the JSContext reference: 我唯一的另一个假设是它与我获取JSContext引用的方式有关:

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

I'm aware that this may not be officially supported by Apple, although I did find at least one SO'er that said he had an Apple-approved app that used that very method. 我知道这可能不会得到Apple的正式支持,尽管我确实发现至少有一个SO'er表示他有一个苹果认可的应用程序使用了这种方法。

EDIT 编辑

I should mention, I implemented UIWebViewDelegate to check the JSContext after each redirect in the UIWebView thusly: 我应该提一下,我实现了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');"];
}

This works in all cases, even when my web page finally loads and reports ios is: undefined the above method simultaneously writes Can see JS from obj c to the Xcode console. 这适用于所有情况,即使我的网页最终加载和报告ios is: undefined上述方法同时写入Can see JS from obj c到Xcode控制台。 This would seem to indicate the JSContext is still valid, and that for some reason it's simply no longer visible from JS. 这似乎表明JSContext仍然有效,并且由于某种原因,它在JS中根本不再可见。


Apologies for the very long-winded question, there is so little documentation on this out there that I figured the more info I could provide, the better. 对这个非常冗长的问题道歉,关于这方面的文档很少,我认为我能提供的信息越多越好。

The page load can cause the WebView (and UIWebView which wraps WebView) to get a new JSContext. 页面加载可能导致WebView(以及包装WebView的UIWebView)获取新的JSContext。

If this was MacOS we were talking about, then as shown in the section on WebView in the 2013 WWDC introduction "Integrating JavaScript into Native Apps" session on Apple's developer network ( https://developer.apple.com/videos/wwdc/2013/?id=615 ), you would need to implement a delegate for the frame load and initialise your JSContext variables in your implementation of the selector for webView:didCreateJavaScriptContext:forFrame: 如果这是我们讨论的MacOS,那么就像2013年WWDC中关于WebView的部分所示,在Apple的开发者网络上将“将JavaScript集成到本机应用程序”会话中显示( https://developer.apple.com/videos/wwdc/2013 /?id = 615 ),您需要为框架加载实现委托,并在webView的选择器实现中初始化JSContext变量:didCreateJavaScriptContext:forFrame:

In the case of IOS, you need to do this in webViewDidFinishLoad: 对于IOS,您需要在webViewDidFinishLoad中执行此操作:

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

The previous JSContext is still available to Objective-C since you've kept a strong reference to it. 之前的JSContext仍然可用于Objective-C,因为您已经保留了对它的强引用。

check this UIWebView JSContext 检查这个UIWebView JSContext

The key point is register a javascript object once JSContext changed. 关键点是JSContext更改后注册一个javascript对象。 I use a runloop observer to check is there any network operation finished, once it finished, I'll get the changed JSContext, and register any object I want to it. 我使用runloop观察器检查是否有任何网络操作完成,一旦完成,我将获得更改的JSContext,并注册我想要的任何对象。

I didn't try if this work for iframe, if u have to register some objects in iframe, try this 我没有尝试if if的这个工作,如果你必须在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);
    };
}];    

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM