本篇文章的示例代碼可以在我的Github上進(jìn)行下載。
在上一篇文章中我們討論了 JavaScriptCore 的基本使用咸包,如何在脫離 UIWebView 的情況下讓 JavaScript 與原生進(jìn)行交互桃序。
但是,在混合開發(fā)過程中烂瘫,我們需要的是讓原生應(yīng)用能與 UIWebView 進(jìn)行流暢的交互媒熊。就如上一篇文章講到的,從 iOS 2 以來(lái)坟比,我們與 UIWebView 進(jìn)行交互的唯一方式就是使用 stringByEvaluatingJavaScriptFromString:方法攔截請(qǐng)求芦鳍,像在 Github 上很火的 WebViewJavascriptBridge 就是使用這一原理來(lái)實(shí)現(xiàn)的。不幸的是葛账,這一現(xiàn)狀在 iOS 7 以后并沒有改變柠衅,雖然蘋果公司在 iOS 7 之后推出了 JavaScriptCore 這個(gè)新工具,但是官方并沒有提供獲取 UIWebView 的 JSContext 方法籍琳。
使用 JavaScriptCore 與 UIWebView 結(jié)合進(jìn)行混合開發(fā)菲宴,這個(gè)需求是如此地合理,以致于我相信不會(huì)只有我一個(gè)人有這種想法巩割。果然裙顽,互聯(lián)網(wǎng)上牛人多,直接使用 Google 一搜宣谈,果然讓我找到別人提供的兩種解決方案愈犹。
注意:本篇文章所描述的方法并非是蘋果官方提供的——可能甚至是他們所不贊成的,這些方法在文章寫作的時(shí)候還是可以使用的闻丑,但是不保證之后會(huì)一直好用漩怎,請(qǐng)留意。
問題描述
在我們需要使用 JavaScript 與原生進(jìn)行交互的時(shí)候嗦嗡,需要一個(gè) JSContext 實(shí)例勋锤。當(dāng)我們使用 JavaScript 代碼開發(fā)自己的功能的時(shí)候,我們可以手動(dòng)創(chuàng)建 JSContext侥祭。
而每個(gè) UIWebView 實(shí)例當(dāng)中都擁有自己的 JSContext 對(duì)象叁执,當(dāng)我們要與 UIWebView 進(jìn)行交互的時(shí)候,第一步就是要獲取它們的 JSContext 對(duì)象矮冬。但是谈宛,蘋果官方并沒有提供獲取 UIWebView 中的 JSContext 對(duì)象的方法。
經(jīng)過搜索之后胎署,發(fā)現(xiàn)兩種比較通用的方法:
- 使用 KVC
- 使用 Category
本篇會(huì)使用一個(gè)示例來(lái)進(jìn)行演示吆录,代碼中只用到了第二種方法,因?yàn)閭€(gè)人覺得第二種方法比較方便琼牧。
使用 KVC
使用這個(gè)方法很簡(jiǎn)單恢筝,簡(jiǎn)單到一句代碼就可以描述清楚:
JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
只要我們能拿到 UIWebView 的實(shí)例哀卫,然后就可以直接使用 KVC 的方法來(lái)獲取它的 JSContext 對(duì)象,就這么簡(jiǎn)單撬槽。
使用分類
第二種方法就是為 NSObject 添加一個(gè)分類此改,并使用這個(gè)分類來(lái)實(shí)現(xiàn) WebKit 的 didCreateJavaScriptContext
回調(diào),這種方法的具體描述可以參考https://github.com/TomSwift/UIWebView-TS_JavaScriptContext侄柔。
具體實(shí)現(xiàn)代碼如下:
@implementation NSObject(JSContextTracker)
+ (NSMapTable *)JSContextTrackerMap {
static NSMapTable *contextTracker;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
contextTracker = [NSMapTable strongToWeakObjectsMapTable];
});
return contextTracker;
}
- (void)webView:(id)unused didCreateJavaScriptContext:(JSContext *)ctx forFrame:(id)alsoUnused {
NSAssert([ctx isKindOfClass:[JSContext class]], @"bad context");
if (!ctx)
return;
NSMapTable *map = [NSObject JSContextTrackerMap];
static long contexts = 0;
NSString *contextKey = [NSString stringWithFormat:@"jsctx_%@", @(contexts++)];
[map setObject:ctx forKey:contextKey];
ctx[@"JSContextTrackerMapKey"] = contextKey; // store the key to the map in the context itself
}
+ (JSContext *)contextForWebView:(UIWebView *)webView {
// this will trigger didCreateJavaScriptContext if it hasn't already been called
NSString *contextKey = [webView stringByEvaluatingJavaScriptFromString:@"JSContextTrackerMapKey"];
JSContext *ctx = [[NSObject JSContextTrackerMap] objectForKey:contextKey];
return ctx;
}
@end
在項(xiàng)目中增加這個(gè)分類之后带斑,以后要獲取 UIWebView 的 JSContext 對(duì)象,只需要使用 [NSObject contextForWebView:myWebView]
就可以了勋拟。
實(shí)例
示例代碼是一個(gè)聯(lián)系人列表,項(xiàng)目里有一個(gè) html 文件妈候,顯示了一個(gè)添加用戶的表單敢靡,在點(diǎn)擊提交之后,將新聯(lián)系人添加到本地的數(shù)組中苦银,并在 UITableView 中顯示出來(lái)啸胧。
同時(shí),代碼里面還演示了上一篇文章中討論過的使用 JavaScript 代碼調(diào)用原生的方法幔虏。
核心代碼在 XGAddContactWebViewController
文件中纺念,這個(gè)控制器里面有一個(gè) UIWebView 實(shí)例,在 viewDidLoad
方法中獲取了 JSContext 對(duì)象:
self.jsContext = [NSObject contextForWebView:self.webView];
然后想括,在 - (void)webViewDidFinishLoad:(UIWebView *)webView
回調(diào)中陷谱,使用這個(gè) JSContext 來(lái)調(diào)用原生的方法:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
[self.loadingView stopAnimating];
[self.jsContext setExceptionHandler:^(JSContext *context, JSValue *value) {
NSLog(@"WEB JS: %@", value);
}];
self.jsContext[@"myStore"] = self.store;
self.jsContext[@"XGContact"] = [XGContact class];
NSString *jsPath = [[NSBundle mainBundle] pathForResource:@"add_a_contact" ofType:@"js"];
NSString *jsCode = [NSString stringWithContentsOfFile:jsPath encoding:NSUTF8StringEncoding error:nil];
[self.jsContext evaluateScript:jsCode];
}
完成的示例代碼請(qǐng)參考我的Github。