【问题标题】:UIWebView JavaScript losing reference to iOS JSContext namespace (object)UIWebView JavaScript 失去对 iOS JSContext 命名空间(对象)的引用
【发布时间】:2014-03-09 23:27:49
【问题描述】:

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

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

  • 我正在 iOS 设备上运行一个 Web 服务器 (CocoaHTTPServer)
  • UIWebView 最初加载一个远程 URL,然后作为应用流程的一部分重定向回 localhost(想想 OAuth)
  • 应用托管的 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>

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

ios 是:未定义

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

最后我的问题是:什么可能/可能导致 JSContext 丢失?我翻阅了 JavaScriptCore 的 .h 文件中的“文档”,发现应该发生这种情况的唯一方法是,如果没有更多 strongJSContext 的引用,但就我而言,我有自己的一个,所以这似乎不对。

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

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

我知道这个可能没有得到 Apple 的官方支持,尽管我确实找到了至少一个 SO'er 说他有一个 Apple 批准的应用程序使用了这种方法。

编辑

我应该提一下,我实现了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');"];
}

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


对于这个冗长的问题,我深表歉意,我认为我可以提供的信息越多越好。

【问题讨论】:

标签: javascript ios objective-c uiwebview javascriptcore


【解决方案1】:

检查这个UIWebView JSContext

关键点是在 JSContext 更改后注册一个 javascript 对象。我使用 runloop 观察器检查是否有任何网络操作完成,一旦完成,我将获取更改后的 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);
    };
}];    

【讨论】:

  • 不确定该链接代码,如果使用 didFinishLoad 并在那里获取上下文会更简单。
  • 是的,这会更简单,但我们注意到在某些情况下它不起作用。
  • 观察注册的 JavaScript 对象在 iOS 8.4 上的每个页面重新加载时消失。该线程中标记的解决方案没有解决我的问题,但这个答案解决了它。我猜苹果最近改变了他们的 API 或其他东西。
【解决方案2】:

页面加载会导致 WebView(以及包装 WebView 的 UIWebView)获取新的 JSContext。

如果这是我们讨论的 MacOS,那么如 Apple 开发者网络 (https://developer.apple.com/videos/wwdc/2013/?id=615) 上的 2013 WWDC 介绍“将 JavaScript 集成到本机应用程序”会话中的 WebView 部分所示,您需要实现一个委托用于框架加载并在 webView:didCreateJavaScriptContext:forFrame:

的选择器实现中初始化 JSContext 变量

在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 仍然可用,因为您已经对它进行了强引用。

【讨论】:

  • 这正是我最终所做的,而且效果很好。我还检查了webViewDidFinishLoad,以确保视图的当前 URL 是“我的”URL,这样我就不会为每个加载的页面设置上下文。
  • 对我来说有意义的是,随着每个新页面的加载,UIWebView 的 window 对象会被重置,因为这是我在执行 self.js[@'ios'] = self 时所附加的内容,所以引用正在丢失.那准确吗?谢谢迈克!
  • 是的。 window 对象在页面加载时是新的,DOM 对象树也是如此。我不清楚页面加载时 JSContext 正在发生什么缓存/重用策略(出于性能原因,我希望会发生一些事情),但很明显 Apple 希望您计划将其替换/新一个在页面加载时创建的创建。如果 Apple 记录了获取 UIWebView 的 JSContext 的正式方法,事情会简单得多。
  • 使用这种技术有时调用上下文会给出 EXC_BAD_ACCESS 有人知道如何解决这个问题吗?
  • 这个代码块在 Swift 中的等价物是什么?我尝试了类似 js["ios"] = self 的方法,但这不起作用。我得到一个编译时错误。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-01-19
  • 2013-10-25
  • 1970-01-01
  • 1970-01-01
  • 2016-06-28
  • 2014-10-15
  • 1970-01-01
相关资源
最近更新 更多