iOS的JavascriptCore的高級使用

正在開發(fā)的APP中一個特殊的需求,需要在APP內(nèi)嵌的webview中使用js調(diào)用一些原生的方法。通過對javascript的分析,發(fā)現(xiàn)能夠很好的解決這個問題,但是也發(fā)現(xiàn)了一些坑,與大家分享。

1.定義接口

通過protocol實(shí)現(xiàn),這一步大家都很清楚,告訴js那邊我有哪些接口。關(guān)鍵是JSExport,這個是我們接口必須遵循的協(xié)議,只有遵循這個協(xié)議,才能將方法爭取的注射到webview中。

@protocol WebViewBridgeDelegate <JSExport>

- (void)showURL:(NSString *)url;

- (NSString*)callDK:(NSString*)func function:(NSString*)param;

@end

使用中發(fā)現(xiàn),這些方法好像不能使用optional,沒有進(jìn)行嚴(yán)格的驗(yàn)證。

2.實(shí)現(xiàn)接口

只需要實(shí)現(xiàn)WebViewBridgeDelegate中的這些方法就可以了。

#import <UIKit/UIKit.h>
#import "WebViewBridgeDelegate.h"

@interface AppJSNative : NSObject<WebViewBridgeDelegate>

@property (nonatomic, weak) UINavigationController  *navigationController;

@end
@implementation LoldkJSNative

- (void)showURL:(NSString *)url
{
    NSLog(@"showURL:%@", url);
 [JumpHandler jumpToURI:url navigationController:self.navigationController];
}

- (NSString*)callDK:(NSString*)func function:(NSString*)param
{
 NSLog(@"%@", func);
 if ([func isEqualToString:@"getToken"])
 {
  NSString *token =xxx;
  NSMutableDictionary *result = [[NSMutableDictionary alloc]init];
  if (!IsStrEmpty(token))
  {
   [result setObject:token forKey:@"token"];
   [result setObject:@(YES) forKey:@"result"];
  }
  else
  {
   [result setObject:@(NO) forKey:@"result"];
  }
  
  NSLog(@"%@", token);
  
  return [result JSONString];
 }
 
    NSMutableDictionary *result1 = [[NSMutableDictionary alloc]init];
    [result1 setObject:@(NO) forKey:@"result"];
 return [result1 JSONString];
}

@end

其中主要是實(shí)現(xiàn)了一些原生頁面跳轉(zhuǎn)的功能,以及獲取原生數(shù)據(jù)的的方法。

3. 注射到webview

- (void)webViewDidStartLoad:(UIWebView *)webView
{
    self.jsContext = [_webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    
    AppJSNative *jsNative = [[AppJSNative alloc]init];
    jsNative.navigationController = self.navigationController; 
    self.jsContext[@"iOS"] = jsNative;
    
    self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exception) {
        context.exception  = exception;
        NSLog(@"jsContext錯誤:%@",exception);
    };
}

為什么我要在webViewDidStartLoad中進(jìn)行注射而不是webViewDidEndLoad中。因?yàn)樵趈s加載的時候就會調(diào)用原生方法,在webViewDidEndLoad中注射會導(dǎo)致JS找不到對應(yīng)的方法。
保存navigationController是為了方便進(jìn)行頁面跳轉(zhuǎn)。

4. JSContext生成時機(jī)的問題

? ? ? ?根據(jù)上一步的確能夠處理大部分問題,但是在實(shí)際使用過程中發(fā)現(xiàn),在windows.onload函數(shù)中調(diào)用的時候,發(fā)現(xiàn)原生的接口不存在。根據(jù)分析發(fā)現(xiàn)是因?yàn)镴SContext對象只會在onload函數(shù)之后才能獲取到。此處我們需要自己找到一種方法,能夠在正確的時機(jī)(越早越好)獲取到JSContext對象。

? ? ? ?基本原理是這樣的:WebKit用WebFrameLoadDelegate回調(diào)與客戶端進(jìn)行通訊就好像UIWebView傳達(dá)頁面加載事件通過他自己的UIWebViewDelegate。WebFrameLoadDelegate其中一個方法是webView:didCreateJavaScriptContext:forFrame:就像所有事件源,WebKit的代碼去檢測他的代理是否實(shí)現(xiàn)了回調(diào)方法,如果實(shí)現(xiàn)了就調(diào)用此方法。
證實(shí)在iOS,UIWebView內(nèi),不論任何對象實(shí)現(xiàn)WebKit的WebFrameLoadDelegate方法,并不是真的實(shí)現(xiàn)webView:didCreateJavaScriptContext:forFrame:所以WebKit從不會調(diào)用此方法。如果此方法存在于代理對象中,它將會被自動調(diào)用。

? ? ? ?既然如此,在OC中有很多的辦法給現(xiàn)有的類和對象動態(tài)的增添一個方法。最簡單的辦法就是通過擴(kuò)展。我給已有的類NSObject添加一個擴(kuò)展去實(shí)現(xiàn)webView:didCreateJavaScriptContext:forFrame:方法。

? ? ? ?的確,添加這個方法讓W(xué)ebKit開始調(diào)用它,因?yàn)槿魏螌ο?包括UIWebView中的一些sink object)都繼承自NSObject,現(xiàn)在都實(shí)現(xiàn)了webView:didCreateJavaScriptContext:forFrame:這個方法。如果未來UIWebView內(nèi)部的sink object實(shí)現(xiàn)了這個代理方法,那么這個途徑就是失效因?yàn)槲覀冏约簩?shí)現(xiàn)的分類永遠(yuǎn)不會被調(diào)用。

? ? ? ?當(dāng)我們的方法被WebKit調(diào)用的時候會傳給我們一個WebKit中的WebView(不是UIWebView),一個JavaScriptCore的JSContext對象和WebKit的WebFrame。因?yàn)闆]有一個公開的WebKit框架的頭文件提供給我們,所以WebView和WebFrame對我們來說非常透明。但是JSContext正是我們尋找的,通過JavaScriptCore框架對我們來說完全是適用的。(在實(shí)際中,我最終在WebFrame中調(diào)用方法,作為一個最佳狀態(tài))

? ? ? ?問題現(xiàn)在就變成怎樣根據(jù)JSContext反找到對應(yīng)的UIWebView。首先我嘗試使用WebView對象我們控制和沿著繼承的view去找到他擁有的UIWebView.但是后來證明這個對象是一些UIView的代理,并不是一個真正的UIView。并且因?yàn)樗麑ξ覀儊碚f是透明的,我也沒有打算使用它。

? ? ? ?我的解決方案是迭代所有在app中所創(chuàng)建的UIWebViews(參考代碼,我是怎么樣做的)并且使用stringByEvaluatingJavaScriptFromString:去儲存一個token"cookie"在JavaScriptContext中,然后我在JSContext中查找已經(jīng)存在的這個token,如果他存在這個UIWebView就是我所要找的。

? ? ? ?一旦我們有了JSContext我們就可以做一些很有趣的事情。我的測試App展示了我們怎樣映射ObjectiveC的blocks和對象到全局命名空間并且通過JavaScript訪問和調(diào)用它們。

5. JS調(diào)用

window.iOS. showURL("app://user?id=1");
var token = window.iOS. callDKFunction("token");

特別注意的是第二個方法,我們OC中實(shí)現(xiàn)的方法叫callDK, 但是由于多參數(shù)的問題,在JS中對應(yīng)的方法名不再是callDK,而是將后面的參數(shù)的名字加在了函數(shù)名后面組成了新的函數(shù)名callDKFunction,這個地方耗費(fèi)了我很久的時間。

6. 回調(diào)

我們也是可以在js中傳入函數(shù)當(dāng)做參數(shù)的,在OC中可以調(diào)用

- (void)processRequestWithData:(NSDictionary *)diconary callback:(JSValue *)value{
    NSLog(@"%@",diconary);
    NSMutableArray *array = [NSMutableArray array];
 [array addObject:[NSArray arrayWithObjects:@{@"key1":@"value1"},@{@"key2":@"value2"},nil]];
    [value callWithArguments:array];
}

其中value就是js傳入的一個回調(diào)函數(shù)。

總結(jié)

以上這些例子,包含了原生頁面調(diào)用、直接return值、回調(diào)。已經(jīng)涵蓋了絕大部分我們想通過js調(diào)用原生的場景。十分的方便。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 跟原生開發(fā)相比,H5的開發(fā)相對來一個成熟的框架和團(tuán)隊(duì)來講在開發(fā)速度和開發(fā)效率上有著比原生很大的優(yōu)勢,至少不用等待審...
    大沖哥閱讀 1,903評論 0 7
  • 隨著H5技術(shù)的興起,在iOS開發(fā)過程中,難免會遇到原生應(yīng)用需要和H5頁面交互的問題。其中會涉及方法調(diào)用及參數(shù)傳值等...
    Chris_js閱讀 3,237評論 1 8
  • 如果所有的狀態(tài)條都是設(shè)置為白色,使用方法1,如果偶爾的使用為白色,則使用2 1.1 在AppDelegate.m里...
    XieHenry閱讀 415評論 0 1
  • 縱是倦了 提起筆又?jǐn)R下 攤開雙手 我捧著月亮回家 剛到樓下 突然一個趔趄 摔個撲爬 月亮摔碎在花下 向外流淌 滿地...
    冰冰心雨閱讀 177評論 3 7
  • 去年十月份來到了北京,來到了這個很多人曾經(jīng)北漂的地方,我也加入了這個行列,只是那一次,我不再是為了漂,而是為了給自...
    米亞MIA閱讀 256評論 0 0

友情鏈接更多精彩內(nèi)容