web-dev-qa-db-ja.com

UIWebViewJavaScriptがiOSJSContext名前空間(オブジェクト)への参照を失う

私は、WebKitJavaScriptCoreフレームワークを使用してObjective C(iOS 7)とJavaScriptの間の双方向通信を活用する概念実証アプリに取り組んできました。ようやく期待どおりに動作させることができましたが、UIWebViewがJSContextを介して作成したiOSオブジェクトへの参照を失うという状況に遭遇しました。

アプリは少し複雑です、ここに基本があります:

  • IOSデバイス(CocoaHTTPServer)でWebサーバーを実行しています
  • 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は:undefined

...そして、その後のsetContent関数でのios.jsLogの呼び出しにより、未定義のオブジェクト例外が発生するため、残りのJSロジックは終了します。

だから最後に私の質問:JSContextが失われる原因/原因は何ですか? JavaScriptCoreの.hファイルの「ドキュメント」を調べたところ、これが発生する唯一の方法は、strongへのJSContext参照がなくなった場合ですが、私の場合は私は自分のものを持っているので、それは正しくないようです。

私の他の唯一の仮説は、それがJSContext参照を取得する方法に関係しているということです。

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

私はこれがAppleによって公式にサポートされていないかもしれないことを知っていますが、彼がAppleに承認されたと言った少なくとも1人のSO'erを見つけましたまさにその方法を使用したアプリ。

[〜#〜]編集[〜#〜]

言及する必要があります。UIWebViewでリダイレクトするたびにJSContextをチェックするためにUIWebViewDelegateを実装しました。

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

これは、私のWebページが最終的にロードされてios is: undefinedを報告した場合でも、すべての場合に機能します。上記のメソッドは同時にCan see JS from obj cをXcodeコンソールに書き込みます。これは、JSContextがまだ有効であり、何らかの理由でJSから表示されなくなったことを示しているように見えます。


非常に長い質問をお詫びします。これに関するドキュメントはほとんどないので、提供できる情報が多ければ多いほどよいと思いました。

18
Madbreaks

ページの読み込みにより、WebView(およびWebViewをラップするUIWebView)が新しいJSContextを取得する可能性があります。

これが私たちが話していたMacOSの場合、2013 WWDCの紹介「JavaScriptをネイティブアプリに統合する」セッションのWebViewのセクションに示されているように、Appleの開発者ネットワーク( https://developer.Apple.com/videos)/wwdc/2013 /?id = 615 )、フレームロードのデリゲートを実装し、webView:didCreateJavaScriptContext:のセレクターの実装でJSContext変数を初期化する必要があります。 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でも引き続き使用できます。

17
Mike

これを確認してください IWebView 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);
    };
}];    
4
buaacss