2014-02-11 3 views
18

Я работаю над доказательством концепции приложения, которое использует двустороннюю связь между Objective C (iOS 7) и JavaScript с использованием рамки WebKit JavaScriptCore. Я, наконец, смог заставить его работать, как ожидалось, но столкнулся с ситуацией, когда UIWebView теряет ссылку на объект iOS, который я создал через JSContext.UIWebView JavaScript, теряющий ссылку на пространство имен (объект) iOS JSContext

приложение является немного сложным, здесь основы:

  • Я бегу веб-сервер на устройстве IOS (CocoaHTTPServer)
  • UIWebView первоначально загружает удаленный URL, а позже перенаправляется обратно в localhost как часть потока приложения (думаю OAuth)
  • HTML-страница, на которую приложение хостов (на локальном хосте) имеет JavaScript, который должен быть разговор с моим кодом IOS

Вот сторона 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 в ViewController в:

-(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 в единственный. Очень круто. Но в одном конкретном случае, 100% времени, это терпит неудачу после определенного количества переадресаций в UIWebView, и после того, как выше страница, наконец загружает он говорит:

Иос: не определено

... и остальная часть логики JS завершается, потому что последующий вызов ios.jsLog в функции setContent приводит к неопределенному исключению объекта.

Итак, наконец, мой вопрос: что может/может привести к потере АОНтекта? Я вырыл «документацию» в файлах .h файлов JavaScriptCore и обнаружил, что единственный способ, которым это должно случиться, - это, если больше нет strong ссылок на JSContext, но в моем случае у меня есть один из моих собственных, так что Кажется правильным.

Моя единственная другая гипотеза состоит в том, что он должен делать с тем, как я приобретающего JSContext ссылка:

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

Я знаю, что это не может быть официально поддерживается Apple, хотя я нашел хотя бы один SO'er, который сказал, что у него есть одобренное Apple приложение, которое использовало этот самый метод.

EDIT

Я должен упомянуть, я реализовал 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 (и UIWebView, который обернет WebView), получит новый JSContext.

Если бы это был MacOS, о котором мы говорили, то, как показано в разделе WebView в вводном документе WWDC 2013 года «Интеграция JavaScript в родные приложения» в сети разработчиков Apple (https://developer.apple.com/videos/wwdc/2013/?id=615), вам нужно будет реализовать делегат для нагрузка кадров и инициализирует переменные JSContext в вашей реализации селектора для Webview: 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; 
} 

Предыдущий AContext по-прежнему доступен для Objective-C, так как вы хорошо указали на него.

+0

Это именно то, что я закончил, и он отлично работает. Я также делаю некоторую проверку в 'webViewDidFinishLoad', чтобы убедиться, что текущий URL-адрес представления является« моим »URL-адресом, так что я не настраиваю контекст для каждой загруженной страницы. – Madbreaks

+0

Что имело смысл для меня: с каждой новой загрузкой страницы объект «окна» UIWebView сбрасывался, и поскольку это то, к чему я привязываюсь, когда я делаю 'self.js [@ 'ios'] = self', ссылка был потерян. Это точно? Спасибо, Майк! – Madbreaks

+3

Да. Объект window является новым при загрузке страницы, также как и дерево объектов DOM. Мне не совсем ясно, какая стратегия кэширования/повторного использования происходит для JSContext при загрузке страницы (я бы ожидал, что что-то произойдет по причинам производительности), но ясно, что Apple ожидает, что вы планируете его заменять/новый один созданный, созданный при загрузке страницы. Все будет намного проще, если Apple задокументирует формальный способ получения JSContext для UIWebView. – Mike

4

проверить это UIWebView JSContext

Ключевым моментом является то зарегистрировать JavaScript Object раз JSContext изменилось. Я использую наблюдателя runloop, чтобы проверить, завершена ли какая-либо операция в сети, как только она закончится, я получу измененный JSContext и зарегистрирую любой объект, который я ему хочу.

Я не пробовал, если эта работа для фрейма, если у вас зарегистрировать некоторые объекты в 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. Помеченное решение в этой теме не решило мою проблему, но этот ответ исправил ее. Я думаю, Apple изменила свои API или что-то недавно. – Martin

Смежные вопросы