iOS開發(fā)--iOS與JS交互的幾種方式

Demos

ObjcAndJSDemo1
ObjcAndJSDemo2

iOS與JS交互的幾種方式

  • JavaScriptCore:iOS7之后出現(xiàn)的,學(xué)習(xí)成本不高,是適配iOS7的首選。
  • 攔截協(xié)議:攔截協(xié)議需要雙方共同協(xié)商為協(xié)議規(guī)定一套準(zhǔn)則,在交互中要遵循該準(zhǔn)則。攔截協(xié)議不需要引入任何框架,適合多個(gè)平臺使用。協(xié)議可以如此定義:schemes://model/action?{參數(shù)1}={數(shù)值1}&{參數(shù)2}={數(shù)值2}&...
  • 第三方框架WebViewJavaScriptBridge:基于攔截協(xié)議進(jìn)行的封裝,學(xué)習(xí)成本相對JavaScriptCore較高,使用不如JavaScriptCore方便。
  • WKWebView:iOS8之后出現(xiàn)的。

iOS7之前,Objective-C調(diào)用JavaScript代碼

iOS7以前,Objective-C調(diào)用JavaScript的方式只有一種,就是通過UIWebView對象的stringByEvaluatingJavaScriptFromString:方法。

  • stringByEvaluatingJavaScriptFromString:方法只能在主線程執(zhí)行
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
        [self.webView stringByEvaluatingJavaScriptFromString:@"var javascript = 1 + 2"];
});  
  • 通過stringByEvaluatingJavaScriptFromString:方法可以簡單地調(diào)用系統(tǒng)提供的JavaScript方法
- (void)webViewDidFinishLoad:(UIWebView *)webView{
    NSString *title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
    NSLog(@"%@", title);
}

iOS7之前,JavaScript調(diào)用Objective-C

  • URL請求攔截。

在Objective-C代碼里設(shè)置UIWebViewDelegate代理,實(shí)現(xiàn)代理方法

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

解釋:該方法可以監(jiān)聽到UIWebView中發(fā)出的URL請求,通過與H5協(xié)商一個(gè)URL通信協(xié)議,來攔截指定的URL,做相應(yīng)的操作,并阻止此鏈接的跳轉(zhuǎn)。

例子:
html代碼

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div style="margin-top: 10px">
        <input type="button" value="callPhone" onclick="callPhone()">
    </div>
</body>

<script>
    // 聲明一個(gè)名為callPhone的js函數(shù),其會發(fā)出一個(gè)鏈接為nativejs://callPhone的請求
    function callPhone() {
        window.location.href = 'nativejs://callPhone';
    }

</script>

</html>

objc代碼

/**
 *  在一個(gè)網(wǎng)頁開始加載一個(gè)frame前被調(diào)用
 */
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
    NSLog(@"shouldStartLoadWithRequest-------");
    
    NSString *urlString = request.URL.absoluteString;
    NSRange range = [urlString rangeOfString:@"nativejs://"];
    if (range.location != NSNotFound) { // 攔截URL協(xié)議頭是nativejs的鏈接
        NSLog(@"執(zhí)行原生調(diào)用相機(jī)的方法");
        return NO;// 阻止此鏈接的跳轉(zhuǎn)
    }
    return YES;
}
  • 監(jiān)聽Cookie。

詳見參考鏈接的原生與H5的交互一文。

iOS7之后,JavaScriptCore的引入,使得Objective-C與JavaScript的交互更為容易

JavaScriptCore中常見的幾種類型

  • JSContext:代表JS的執(zhí)行環(huán)境,通過evaluateScript:方法就可以執(zhí)行JS方法。
  • JSValue:封裝了JS與ObjC中對應(yīng)的模型,以及調(diào)用JS的API等。
  • JSExport:一個(gè)協(xié)議,通過遵守此協(xié)議,可以定義我們自己的協(xié)議,在協(xié)議中聲明的API都會在JS中暴露出來,能被JS調(diào)用。
  • JSManagedValue:管理數(shù)據(jù)和方法的類。
  • JSVirtualMachine:處理線程相關(guān),使用較少。

Objective-C調(diào)用JavaScript

/**
 *  網(wǎng)頁加載完畢時(shí)被調(diào)用
 */
- (void)webViewDidFinishLoad:(UIWebView *)webView{
    // 獲取當(dāng)前JS執(zhí)行環(huán)境
    JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    NSString *alertJS = @"alert('Hello JS!')"; //準(zhǔn)備執(zhí)行的JS代碼
    // 通過evaluateScript:方法調(diào)用JS的alert
    [context evaluateScript:alertJS];
}

JavaScript 調(diào)用 Objective-C

  • 直接調(diào)用JS(不適用于實(shí)戰(zhàn)項(xiàng)目中)。

    例子1:
    objc代碼

    // 獲取當(dāng)前JS執(zhí)行環(huán)境
      JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
      
      // context直接執(zhí)行JS代碼。
      [context evaluateScript:@"var num = 10"];
      [context evaluateScript:@"var squareFunc = function(value) { return value * value }"];
      // 計(jì)算正方形的面積
      JSValue *squareArea = [context evaluateScript:@"squareFunc(num)"];
      NSLog(@"squareArea:%@", squareArea.toNumber);
      
      // 也可以通過下標(biāo)的方式獲取到JS函數(shù)
      JSValue *squareFunc = context[@"squareFunc"];
      JSValue *squareArea2 = [squareFunc callWithArguments:@[@"20"]];
      NSLog(@"squareArea2:%@", squareArea2.toNumber);
    

    例子2:
    html代碼

    <div style="margin-top: 10px">
        <input type="button" value="log" onclick="log('測試')">
    </div>
    

    objc代碼

/**

  • 網(wǎng)頁加載完畢時(shí)被調(diào)用
    */
  • (void)webViewDidFinishLoad:(UIWebView *)webView{
    // 獲取當(dāng)前JS執(zhí)行環(huán)境
    JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    // 直接調(diào)用JS代碼
    context[@"log"] = ^(){
    // 取出JS方法的參數(shù)
    NSArray *args = [JSContext currentArguments];
    for (id obj in args) {
    NSLog(@"%@",obj); // 打印JS方法接收到的所有參數(shù)
    }
    };
    }

    
    
  • 在ObjC中通過JSContext注入模型,然后調(diào)用模型的方法。(重要,項(xiàng)目一般用該方式)

第一步:需要聲明一個(gè)與JS進(jìn)行交互的協(xié)議(NativeApisProtocol),要求該協(xié)議遵守JSExport協(xié)議。
第二步:新建一個(gè)模型(NativeAPIs),要求該模型遵守NativeApisProtocol協(xié)議。一般而言,需要在NativeAPIs模型中聲明一個(gè)JSContext屬性,便于與JS交互。
第三步:實(shí)現(xiàn)該模型(NativeAPIs),即在NativeAPIs.m文件中實(shí)現(xiàn)NativeApisProtocol協(xié)議中定義的方法。
第四步:在ViewController.m的-webViewDidFinishLoad:方法中獲取當(dāng)前JS執(zhí)行環(huán)境(self.jsContext),然后將NativeAPIs模型注入到JS執(zhí)行環(huán)境。

注意:NativeApisProtocol協(xié)議中定義的方法是在子線程中執(zhí)行的,如果在所定義的方法中需要修改界面或者跳轉(zhuǎn)之類的,需要通過GCD回主線程操作。

缺陷:通過參考鏈接的JavaScript和Objective-C交互的那些事(續(xù))可知,在-webViewDidFinishLoad:方法中注入NativeAPIs模型存在一定的問題,因?yàn)檫@時(shí)候網(wǎng)頁還沒加載完,JavaScript若開始調(diào)用Objective-C代碼(即NativeApisProtocol協(xié)議中定義的方法),會出現(xiàn)調(diào)用不到方法的問題。
解決方法:在每次創(chuàng)建JSContext環(huán)境的時(shí)候,都注入NativeAPIs模型到JSContext環(huán)境中。更加具體的方法可以參考第三方庫UIWebView-TS_JavaScriptContext。通過引入UIWebView+TS_JavaScriptContext,讓ViewController遵守TSWebViewDelegate協(xié)議,實(shí)現(xiàn)代理協(xié)議中的方法-webView:didCreateJavaScriptContext:,以此獲取JSContext環(huán)境。

objc代碼

/*--  NativeAPIs.h  ---*/ 

#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>
#import <UIKit/UIKit.h>

// 聲明與JS交互的協(xié)議
@protocol NativeApisProtocol <JSExport> // 遵守JSExport協(xié)議

// 調(diào)用系統(tǒng)相機(jī)
- (void)callCamera;

// 調(diào)用系統(tǒng)分享
- (void)share:(NSString *)shareInfo;

// 打開寧波手機(jī)閱讀
- (void)openNBPhoneReader;

@end

@interface NativeAPIs : NSObject <NativeApisProtocol>

@property(weak, nonatomic) JSContext *jsContext;

@end

/*--------------------------------------------*/

/*--  NativeAPIs.m --*/ 

/*--  省略,具體看Demo源碼 --*/

/*--------------------------------------------*/

// ViewController.m

/*-- 省略前面的代碼 --*/

#pragma mark - UIWebViewDelegate

- (void)webViewDidFinishLoad:(UIWebView *)webView{
    NSLog(@"網(wǎng)頁加載完畢------");
    
    // 獲取JS上下文運(yùn)行環(huán)境
    self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    NativeAPIs *nativeAPIs = [[NativeAPIs alloc] init];
    // 將NativeAPIs模型注入JS
    self.jsContext[@"NativeApis"] = nativeAPIs;
    nativeAPIs.jsContext = self.jsContext;
    
    self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
        context.exception = exceptionValue;
        NSLog(@"異常信息: %@", exceptionValue);
    };
}

html代碼

<html>

/*-- 省略無關(guān)緊要的代碼,具體見Demo --*/

<body>
    <div style="margin-top: 10px">
        <input type="button" value="CallCamera" onclick="NativeApis.callCamera()">
    </div>
    <div style="margin-top: 50px">
        <input type="button" value="Share" onclick="callShare()">
    </div>
    <div style="margin-top: 50px">
        <input type="button" value="OpenReader" onclick="NativeApis.openNBPhoneReader()">
    </div>
</body>

<script>
    // 聲明一個(gè)名為picCallback的函數(shù),其參數(shù)為photo
    var picCallback =  function (photo) {
        alert(photo);
    }

    // 聲明一個(gè)名為callShare的函數(shù)
    var callShare = function () {
        var shareInfo = JSON.stringify(
                                      { "title": "objc&js的交互",
                                        "desc": "就是那些事"}
                                      );
        // 調(diào)用原生的share方法
        NativeApis.share(shareInfo);
    }

    // 聲明一個(gè)名為shareCallback的函數(shù)
    var shareCallback =  function () {
        alert('success');
    }

</script>

</html>

參考鏈接

iOS與JS交互實(shí)戰(zhàn)篇(ObjC版)

UIWebView 與 JS 交互(1):Objective-C 調(diào)用 Javascript

原生與H5的交互

Objective-C與JavaScript交互的那些事

JavaScript和Objective-C交互的那些事(續(xù))

JavaScriptCore在實(shí)際項(xiàng)目中的使用的坑

UIWebView中Objective-C與JavaScript交互

UIWebView加載本地HTML文件

OC與JS的交互

iOS中JavaScript和OC交互

UIWebView-TS_JavaScriptContext

關(guān)于UIWebView的總結(jié)

UIWebview 的javascript與ios objective-c互動(dòng)傳參數(shù)

UIWebview 的javascript與ios objective-c互動(dòng)傳參數(shù)(Ⅱ)

js(javascript)與OC(Objective-C)交互

iOS中的HTML交互簡說 -- 推薦

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

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

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