UIWebView JavaScript 丢失对 iOS JSContext 命名空间(对象)的引用
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/21714365/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
UIWebView JavaScript losing reference to iOS JSContext namespace (object)
提问by Madbreaks
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 JavaScriptCoreframework. 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.
我一直致力于使用 WebKit JavaScriptCore框架利用 Objective C (iOS 7) 和 JavaScript 之间的双向通信的概念验证应用程序。我终于能够让它按预期工作,但遇到了 UIWebView 失去对我通过 JSContext 创建的 iOS 对象的引用的情况。
The app is a bit complex, here are the basics:
该应用程序有点复杂,以下是基础知识:
- I'm running a web server on the iOS device (CocoaHTTPServer)
- The UIWebView initially loads a remote URL, and is later redirected back to
localhost
as part of the app flow (think OAuth) - The HTML page that the app hosts (at localhost) has the JavaScript that should be talking to my iOS code
- 我在 iOS 设备 (CocoaHTTPServer) 上运行网络服务器
- UIWebView 最初加载一个远程 URL,然后
localhost
作为应用程序流的一部分重定向回(想想 OAuth) - 应用程序托管的 HTML 页面(在本地主机上)具有应该与我的 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. 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:
现在,在几乎所有情况下,这都能很好地工作,我ios is: object
在 UIWebView 和 Xcode 的控制台中都能看到。很酷。但在一个特定场景中,100% 的情况下,在 UIWebView 中重定向一定次数后,这会失败,并且一旦上述页面最终加载,它会说:
ios is: undefined
ios是:未定义
...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 逻辑的其余部分退出,因为随后ios.jsLog
在setContent
函数中调用会导致未定义的对象异常。
So finally my question: what could/can cause a JSContext to be lost? 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.
所以最后我的问题是:什么可能/可能导致 JSContext 丢失?我翻阅了 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 beofficially 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 说他有一个 Apple 批准的应用程序,它使用了这种方法。
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. This would seem to indicate the JSContext is still valid, and that for some reason it's simply no longer visible from JS.
这适用于所有情况,即使我的网页最终加载并报告ios is: undefined
上述方法同时写入Can see JS from obj c
Xcode 控制台。这似乎表明 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.
为这个冗长的问题道歉,关于这个的文档太少了,我想我能提供的信息越多越好。
采纳答案by Mike
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 介绍“将 JavaScript 集成到本机应用程序”会议中 Apple 开发者网络 ( https://developer.apple.com/videos/wwdc/2013) 中的WebView 部分所示/?id=615),您需要为框架加载实现一个委托,并在webView:didCreateJavaScriptContext:forFrame的选择器实现中初始化您的 JSContext 变量:
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,因为您一直对其进行强引用。
回答by buaacss
check this UIWebView JSContext
The key point is register a javascript object once JSContext changed. 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.
关键点是在 JSContext 更改后注册一个 javascript 对象。我使用 runloop 观察器检查是否有任何网络操作完成,一旦完成,我将获取更改后的 JSContext,并注册我想要的任何对象。
I didn't try if this work for iframe, if u have to register some objects in iframe, try this
我没有尝试这是否适用于 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);
};
}];