天天看點

iOS 開發中 JS 與 Native 的互動方式

在 iOS 開發中,JS 與 Native 的互動分為兩種,第一種是 Native 調 JS,即通過在 Native 代碼中執行 JS 達到在 webkit 控件中展現相應 JS 代碼的效果;另一種就是 JS 調用 Native ,通過 web 前段 JS 的執行來調用 Native 本地的方法,用以實作例如開啟照相機、資料持久化等等隻能通過 Native 代碼實作的效果。

目前進行 JS 和 Native 互動主要有兩種方式,下面進行一一介紹:

一、WebView 方法/代理方法

通常來說,iOS 中實作加載 web 頁面主要有兩種控件,UIWebView 和 WKWebview,兩種控件對應具體的實作方法不同,我們在這裡分開進行介紹:

UIWebView控件

  • Native 調用 JS:

在 Native 中執行 JS 語句非常簡單, JS 作為腳本語言它的執行需要解釋器的存在,即浏覽器,是以 UIWebView 作為浏覽器控件,提供了 native 調用 JS 的對象方法:

//script 是要執行的 JS 語句
//傳回值為 JS 執行結果,如果 JS 執行失敗則傳回 nil,如果 JS 執行沒有傳回值,則傳回值為空字元串
- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script; 
           

這裡編寫了一個 demo 僅供參考:

- (void)webViewDidFinishLoad:(UIWebView*)webView
{
    NSString* str = [self.webView stringByEvaluatingJavaScriptFromString:@"pageDidLoad()"]; NSLog(@"%@", str); } 
           

當 WebView 加載完畢的時候調用 JS 中的

pageDidLoad

方法,并在控制台列印 JS 的執行結果。

  • JS 調用 Native:

    使用 WebView 方法/代理方法完成 JS 調用 Native 要稍微複雜一點,需要 Native前端和 web 前端的良好配合,主要原理是通過 UIWebVIew 的代理方法截取 web 前端的跳轉請求,通過識别與 web 前端約定好的自定義協定頭來判斷本次請求是否為 JS 調用 Native 的請求,來調用對應的 Native 方法。

    其中涉及到的 UIWebView 代理方法為:

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;

           

下面通過例子來進行示範:

JavaScript 代碼:

OC代碼:

- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
{
    //dzbridge 為約定好的協定頭,如果是,則頁面不進行跳轉 if ([request.URL.scheme isEqualToString:@"dzbridge"]) { //截取字元串來判斷是否存在參數 NSArray<NSString*>* arr = [request.URL.absoluteString componentsSeparatedByString:@"?"]; if (arr.count > 
           

WKWebView控件

iOS8 以後,蘋果推出了新架構 WKWebKit, 其中提供了可以替換 UIWebView 的元件 WKWebView。原來 UIWebView 的各種問題得到了改善,速度更快了,占用記憶體少了(模拟器加載百度與開源中國網站時,WKWebView 占用23M,而UIWebView 占用85M),目前來看,WKWebView 是 App 内部加載網頁更佳的選擇!

WKWebView 相對 UIWebView 做了較大幅度的重構,将 UIWebViewDelegate 與 UIWebView 重構成了14類與3個協定,是以,在 WKWebView 中進行 JS 與 Native 的互動與 UIWebView 相比也有較大的不同。

  • Native 調用 JS:

    在 WKWebView 中 Native 調用 JS 的方式與 UIWebview 中比較相似,也是通過自己本身的一個對象方法:

// javaScriptString 為待執行的 JS 語句
// completionHandler 為執行 JS 完畢後的回調,block 的第一個參數為執行結果,第二個參數為錯誤
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ __nullable)(__nullable id, NSError * __nullable error))completionHandler; 
           

看下面一個小例子:

#pragma mark----- WKNavigationDelegate -----

- (void)webView:(WKWebView*)webView didFinishNavigation:(WKNavigation*)navigation
{
    [self.webView evaluateJavaScript:@"pageDidLoad()" completionHandler:^(id _Nullable value, NSError* _Nullable error) { NSLog(@"%@", value); }]; } 
           
  • JS 調用 Native:

    WKWebView 中 JS 調用 Native 與 UIWebView 有着比較大的不同,首先需要介紹幾個類(/協定/屬性):

  1. WKWebViewConfiguration

    :是 WKWebView 初始化時的配置類,裡面存放着初始化 WK 的一系列屬性;
  2. WKUserContentController

    :為 JS 提供了一個發送消息的通道并且可以向頁面注入 JS 的類;
  3. WKScriptMessageHandler

    :一個協定,協定中隻有一個方法,這個方法是頁面執行特定 JS 的一個回調,這個特定的 JS 格式為:

    window.webkit.messageHandlers.<name>.postMessage(<messageBody>)

WKWebViewConfiguration

作為 WK 的配置類,其中有一個屬性為

@property (nonatomic, strong) WKUserContentController *userContentController;

           

WKUserContentController

的一個執行個體,

WKUserContentController

有一個對象方法為:

/*! @abstract Adds a script message handler.
 @param scriptMessageHandler The message handler to add.
 @param name The name of the message handler.
 @discussion Adding a scriptMessageHandler adds a function
 window.webkit.messageHandlers.<name>.postMessage(<messageBody>) for all
 frames.
 */
- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name; 
           

從蘋果給出的注釋來看,通過該方法能夠添加一個腳本消息的處理器,即

(id <WKScriptMessageHandler>)scriptMessageHandler

,另外還能發現,添加腳本處理器後,需要在 JS 中添加

window.webkit.messageHandlers.<name>.postMessage(<messageBody>)

才能起作用。

demo:

// 建立并配置 WKWebView 的相關參數
WKWebViewConfiguration* config = [[WKWebViewConfiguration alloc] init];
WKUserContentController* userContent = [[WKUserContentController alloc] init]; // self 指代的對象需要遵守 WKScriptMessageHandler 協定 [userContent addScriptMessageHandler:self name:@"test"]; config.userContentController = userContent; 
           

在頁面上的 JS 執行

window.webkit.messageHandlers.<name>.postMessage(<messageBody>)

時,被添加的

ScriptMessageHandler

就會執行實作的

WKScriptMessageHandler

協定的方法,例如:

#pragma mark----- WKScriptMessageHandler -----
/**
 *  JS 調用 OC 時 webview 會調用此方法
 *
 *  @param userContentController  webview 中配置的 userContentController 資訊
 *  @param message                js 執行傳遞的消息
 */
- (void)userContentController:(WKUserContentController*)userContentController didReceiveScriptMessage:(WKScriptMessage*)message { NSLog(@"%@", message); } 
           

在代理方法中實作相應的 Native 代碼,即完成了 JS 調用 Native 的過程。

二、JavaScriptCore

OS X Mavericks 和 iOS 7 引入了 JavaScriptCore 庫,把 WebKit 的 JavaScript 引擎用 Objective-C 封裝,提供了簡單,快速以及安全的方式接入 JavaScript。

JavaScriptCore中類及協定

  • JSContext:JavaScript 運作的上下文環境
  • JSValue:JavaScript 和 Objective-C 資料和方法的橋梁
  • JSExport:這是一個協定,如果采用協定的方法互動,自己定義的協定必須遵守此協定
  • JSManagedValue:管理資料和方法的類
  • JSVirtualMachine:處理線程相關,使用較少

JavaScript 調用 Native

使用 JavaScriptCore 進行 JS 和 Native 的互動,無論想要實作什麼樣的效果都需要獲得一個有效的 JSContext 執行個體,即一個有效的 JS 運作的上下文(這一步驟以下不再重複提及)。

  • 獲得目前的 JSContext:

    可以在頁面加載完畢後,采用 KVC 的方式從webView 中獲得,如下:

JSContext* jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

           
  • 将想要被暴露給 JS 的方法抽象成為一個協定(protocol),該協定需要遵守

    JSExport

    協定:
@protocol JSObjcDelegate <JSExport>
- (void)callCamera; - (NSString*)share:(NSString*)shareString; @end 
           
  • 将要暴露給 JS 的對象的類需要遵守自定義的協定,如上:

    JSObjcDelegate

  • 将 OC 對象橋接到 JS 環境中,并設定異常處理
// 将本對象與 JS 中的 DZBridge 對象橋接在一起,在 JS 中 DZBridge 代表本對象
[self.jsContext setObject:self forKeyedSubscript:@"DZBridge"];
self.jsContext.exceptionHandler = ^(JSContext* context, JSValue* exceptionValue) { context.exception = exceptionValue; NSLog(@"異常資訊:%@", exceptionValue); }; 
           
  • 在 JS 中通過 DZBridge 調用本對象暴露出的方法:

Native 調用 JavaScript

  • 第一種方式同 UIWebView 中類似,都是直接執行 JS 字元串,通過 JSContext 執行 JS 代碼:
[self.jsContext evaluateScript:@"alert(\"執行 JS\")"];

           
  • 另一種方式适用于執行 web 頁面上已有的方法,通過 JSValue 來調用 JS 中的方法,JSValue 是 JavaScript 中值得一個引用,他可能包裝着一個 JavaScript 的方法,通過

    callWithArguments:

    方法進行調用,例如:
JSValue* picCallback = self.jsContext[@"picCallback"];
[picCallback callWithArguments:@[ @"photos" ]];
           

轉載于:https://www.cnblogs.com/holyday/p/8982171.html

繼續閱讀