2014-02-11 84 views
18

我一直在研究一個概念驗證應用程序,它利用Objective C(iOS 7)和JavaScript之間的雙向通信,使用WebKit JavaScriptCore框架。我終於能夠按預期工作,但遇到了UIWebView失去對我通過JSContext創建的iOS對象的引用的情況。UIWebView JavaScript失去對iOS的引用JSContext命名空間(對象)

的應用程序是一個有點複雜,這裏有最基礎的:

  • 我跑(CocoaHTTPServer)iOS設備上的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 

以及在相關的視圖控制器的.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> 

現在,幾乎在所有情況下,這工作很漂亮,我看到ios is: object無論是在一個UIWebView和Xcode的CON唯一。很酷。但是,在一個特定的情況下,時間的100%,這失敗一定數量的UIWebView的重定向後,一旦上述頁面最後裝入它說:

的IOS是:未定義

...並且JS邏輯的其餘部分退出,因爲隨後調用ios.jsLog中的setContent函數會導致未定義的對象異常。

因此,最後我的問題:什麼可能會導致JSContext丟失?我通過JavaScriptCore的.h文件中的「文檔」挖掘並發現,應該發生這種情況的唯一方法是,如果沒有更多strong引用JSContext,但在我的情況下,我擁有自己的一個,所以doesn看起來不錯。

我唯一的另一個假設是,它與在我取得JSContext參考的方式做:我知道,這可能不是蘋果官方支持

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

,儘管我確實發現至少有一位說他有一個蘋果認可的應用程序使用這種方法。

編輯

我應該提,我實現UIWebViewDelegate檢查JSContext後的UIWebView的每個重定向正是如此:

-(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中不再可見。


道歉很囉嗦的問題,對這個這麼少的文檔,在那裏,我想更多的信息,我可以提供,效果更佳。

+0

嗨Madbreaks,我有相關的JS和IOS之間的通信問題。如果您有任何想法解決,請讓我知道。下面是我的鏈接「http://stackoverflow.com/questions/27120280/communication-between-javascript-and-objective-c-ios」 – Mahesh

回答

17

頁面加載可以導致WebView(和包裝WebView的UIWebView)獲取新的JSContext。

如果這是我們正在討論的MacOS,那麼正如Apple開發人員網絡(https://developer.apple.com/videos/wwdc/2013/?id=615)中2013年WWDC簡介中介紹的「將JavaScript集成到原生應用程序」會話中的WebView部分所示,您需要實現一個委託框架加載和初始化你JSContext變量在您的實現選擇爲web視圖: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; 
} 

以前的JSContext仍然可用於Objective-C,因爲您已經對其保持了強烈的參考。

+0

這正是我最終做的,它很好。我還在'webViewDidFinishLoad'中做了一些檢查,以確保視圖的當前URL是「我的」URL,這樣我就不會爲每個加載的頁面設置上下文。 – Madbreaks

+0

對我來說有意義的是,在每次加載新頁面時,UIWebView的'window'對象被重置,並且因爲這是我在執行'self.js [@'ios'] = self'時附加的內容,所以引用正在丟失。這是否準確?謝謝邁克! – Madbreaks

+3

是的。窗口對象在頁面加載時是新的,就像DOM對象樹一樣。我並不清楚JSContext在頁面加載時發生了什麼樣的緩存/重用策略(我希望會出現一些性能方面的原因),但它清楚地表明,Apple希望你計劃將它取代/新的在頁面加載時創建一個創建。如果Apple記錄了一個正式的方法來獲取UIWebView的JSContext,情況會簡單得多。 – Mike

4

檢查此UIWebView JSContext

的關鍵點是寄存器javascript對象一次JSContext改變。我使用runloop觀察器來檢查是否有任何網絡操作完成,一旦完成,我將獲取已更改的JSContext,並註冊我想要的任何對象。

我沒有嘗試,如果這項工作的iframe,如果u有在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); 
    }; 
}];  
+0

不確定那個鏈接的代碼,只是使用didFinishLoad並在那裏獲取上下文會更簡單。 – malhal

+0

是的,那會更簡單,但我們注意到,對於某些不起作用的情況。 – buaacss

+1

注意到註冊的JavaScript對象在iOS 8.4上的每個頁面上消失。這個線程中標記的解決方案並沒有解決我的問題,但這個答案解決了它。我想蘋果公司最近改變了他們的API或其他東西。 – Martin