iOS與JS交互總結(jié)

iOS與JS交互總結(jié)

近幾年來移動開發(fā)使用網(wǎng)頁嵌入形式的越來越多,這就不可避免的出現(xiàn)原生控件和網(wǎng)頁頁面的JS交互,本篇就大概總結(jié)一下目前iOS開發(fā)中原生控件與JS的交互的幾種形式。</br>

iOS開發(fā)中使用的是UIWebView控件來加載網(wǎng)頁頁面資源的。所以我們也就主要圍繞這個控件來總結(jié)一下,大體上可以分為三種形式:

1、原生api交互,直接執(zhí)行腳本,攔截代理
2、第三方庫WebViewJavascriptBrige交互,實質(zhì)還是攔截代理
3、JavaScriptCore框架
? 3.1 context上下文,context上下文直接設置和調(diào)用
? 3.2 JSExport協(xié)議,通過JSExport協(xié)議設置和調(diào)用


接下來就一個一個的來看看到底是如何實現(xiàn)的,大家也可以提前把demo下載下來,結(jié)合代碼來看效果會更好哦。

原生api交互

使用原生API來實現(xiàn)交互,實際上主要用到一個函數(shù)和一個協(xié)議,
函數(shù):

- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;

協(xié)議:

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
1、 OBJC調(diào)用JS

直接使用UIWebView的函數(shù)執(zhí)行JS代碼,
比如我們JS代碼中有一個函數(shù)objcCallJS,

function objcCallJS() {
        var data = 'ljt'
        alert('來自objc的調(diào)用,有返回值:' + data);
        return data
}

那我們該如何去調(diào)用呢?
直接在相應的地方如此調(diào)用即可:

NSString *returnStr = [self.homeWebView stringByEvaluatingJavaScriptFromString:@"objcCallJS()"];
    NSLog(@"返回值為:%@",returnStr);

我們注意到這個地方會有個返回值,此時如果我們調(diào)用的JS代碼有返回值得話,就會賦值到returnStr這個變量,可供我們后續(xù)使用。如果JS代碼沒有返回值得話,這個值默認是Undefine。
OBJC調(diào)用JS代碼就是如此的簡單。這個時候,有人說了,如果我們想向JS代碼中傳值該怎么弄呢?
JS代碼:

function objcCallJSParam(param1,param2) {
        alert('來自objc的調(diào)用,帶有參數(shù):' + param1 + '  ' + param2);
}

OBJC代碼:

- (void)callJSBtnParamAction:(id)sender {
    NSLog(@"開始調(diào)用JS函數(shù),帶有參數(shù)");
    [self.homeWebView stringByEvaluatingJavaScriptFromString:@"objcCallJSParam('ljt','ths')"];
}

這樣我們就可以向JS代碼中傳入?yún)?shù)了。

2、 JS調(diào)用OBJC

在這里我們使用攔截UIWebView的代碼來實現(xiàn),我們知道代理函數(shù)- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;會在UIwebView開始加載頁面的時候調(diào)用,如果我們返回NO則這個頁面什么也不做,如果返回YES則加載這個頁面。有了這個特性,我們就可以來實現(xiàn)JS調(diào)用OBJC的代碼。
</br>具體的流程: 頁面響應時,重新設置頁面的href,并將OBJC所用的參數(shù)以及所調(diào)用的指定函數(shù),封裝成一個特定格式的URL鏈接,然后我們在UIwebView的協(xié)議中攔截這個鏈接,然后解析出來,根據(jù)固定的格式做出相應的處理。
具體來說:當我們頁面中一個事件,設置了頁面的href,攜帶了兩個參數(shù),

//調(diào)用OBJC
function JSCallObjc() {
    window.location.href="www.baidu.com/param=ljt&ths";
}

我們在OBJC中可以這么做:

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    NSLog(@"將要開始加載頁面");
    NSString *urlStr = [[request URL] absoluteString];
    if ([urlStr containsString:@"param"]) {
        NSRange range = [urlStr rangeOfString:@"param="];
        NSString *paramStr = [urlStr substringFromIndex:range.location + range.length];
        NSLog(@"param=%@",paramStr);
        UIAlertController *alterVC = [UIAlertController alertControllerWithTitle:@"objc彈框" message:paramStr preferredStyle:UIAlertControllerStyleAlert];
        UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
        [alterVC addAction:okAction];
        [self presentViewController:alterVC animated:YES completion:nil];
        return NO;
    }
    return YES;
}

這樣就實現(xiàn)了JS調(diào)用OBJC的效果,當然這個例子不是那么好,href的格式我是隨便寫寫,當真正使用起來的時候需要事先設計好合理的封裝方式。
</br></br>

第三方庫WebViewJavascriptBrige交互

這個部分我們使用有名的第三方庫WebViewJavascriptBrige來介紹一下,關于這個庫的實現(xiàn),網(wǎng)上已經(jīng)有很多介紹,關于原理的實現(xiàn)就不多介紹了,直接進入使用環(huán)節(jié),使用這個庫的時候有一個地方需要注意一下,在所加載的頁面中需要實現(xiàn)固定的寫法:

//固定函數(shù),必須這樣寫
    function setupWebViewJavascriptBridge(callback) {
        if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
        if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
        window.WVJBCallbacks = [callback];
        var WVJBIframe = document.createElement('iframe');
        WVJBIframe.style.display = 'none';
        WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__';
        document.documentElement.appendChild(WVJBIframe);
        setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
    }

    //所有交互的函數(shù)都寫在這里面
    setupWebViewJavascriptBridge(function(bridge) {
        ····
    })
1、 OBJC調(diào)用JS

首先我們需要在JS中注冊一個函數(shù),供OBJC來調(diào)用

bridge.registerHandler('objcCallJS', function(data, responseCallback) {
            myAlert(data)
            var responseData = { 'Javascript Says':'Right back atcha!' }
            responseCallback(responseData)
})

這樣注冊之后,我們就可以在OBJC代碼中調(diào)用這個名為objcCallJS的函數(shù)了。

OBJC中的代碼:

- (void)callJSBtnAction:(id)sender {
    NSLog(@"調(diào)用JS函數(shù)");
    //callHandler有幾種形式
    //- (void)callHandler:(NSString *)handlerName 只調(diào)用函數(shù)
    //- (void)callHandler:(NSString *)handlerName data:(id)data 調(diào)用的同時攜帶數(shù)據(jù)
    //- (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback 不但調(diào)用和攜帶數(shù)據(jù),而且設置回調(diào)函數(shù)處理所需的數(shù)據(jù)(如果需要處理結(jié)果數(shù)據(jù))
//    [self.bridge callHandler:@"objcCallJS" data:@{@"key":@"value"} responseCallback:^(id responseData){
//        NSLog(@"%@",responseData);
//    }];
//    [self.bridge callHandler:@"objcCallJS"];
    [self.bridge callHandler:@"objcCallJS" data:@{@"key":@"value"}];
    
}

這個地方有幾處我們需要說明的地方
1、objcCallJS這個名稱必須是一樣的
2、OBJC中調(diào)用JS的函數(shù)是通過- (void)callHandler:(NSString*)handlerName data:(id)data;這個借口調(diào)用的,這個接口的最終調(diào)用實現(xiàn)是- (void)callHandler:(NSString*)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback;,當我們調(diào)用的時候,<strong>handlerName</strong>對應JS代碼中注冊的函數(shù)名,<strong>data</strong>對應JS代碼中的data,即為OBJC向JS中傳入的參數(shù),還有一個有意思的參數(shù)就是<strong>responseCallback</strong>,就是JS代碼的responseCallback,也就是說,當我們的JS代碼處理完成之后,OBJC還有一次機會可以對JS代碼處理完成的邏輯進行處理。當然data和responseCallback這兩個參數(shù)都不是必須的。

2、 JS調(diào)用OBJC

與OBJC調(diào)用JS的邏輯類似,

首先我們需要在OBJC中注冊JS能夠調(diào)用的函數(shù):

//注冊js調(diào)用函數(shù),并設定回調(diào)。js中可以調(diào)用JSCallObjc的函數(shù)
    [self.bridge registerHandler:@"JSCallObjc" handler:^(id data, WVJBResponseCallback responseCallback){
        NSString *paramStr = [data objectForKey:@"key"];
        UIAlertController *alterVC = [UIAlertController alertControllerWithTitle:@"objc彈框" message:paramStr preferredStyle:UIAlertControllerStyleAlert];
        UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
        [alterVC addAction:okAction];
        [self presentViewController:alterVC animated:YES completion:nil];
        responseCallback(@"Response from testObjcCallback");
    }];

注冊完成以后,我們就可以在JS代碼中調(diào)用這個注冊好的函數(shù)了:

//調(diào)用OBJC中的函數(shù)
        var callbackButton = document.getElementById('btn')
        callbackButton.onclick = function(e) {
            e.preventDefault()
                                 
            //callHandler的參數(shù)可變
            //bridge.callHandler('JSCallObjc') 沒有攜帶數(shù)據(jù)
            //bridge.callHandler('JSCallObjc', {'key': 'value'}) 攜帶數(shù)據(jù)
            //bridge.callHandler('JSCallObjc', {'key': 'value'},function(response) {
            //      log('JS got response', response)
            //  }) 攜帶參數(shù)和回調(diào)函數(shù)
                   
            //bridge.callHandler('JSCallObjc')              
            bridge.callHandler('JSCallObjc',{'key': 'value'})
            
            
        }

我們可以看到,這個邏輯和接口與OBJC調(diào)用JS的邏輯和接口大同小異,具體的參數(shù)也是差不多的。
到此我們就可以利用這個第三方庫來實現(xiàn)OBJC和JS的互相調(diào)用。其實這個庫的內(nèi)部還是通過攔截協(xié)議來實現(xiàn)這個交互過程的。

JavaScriptCore框架

JavaScriptCore框架是在iOS7之后引入的框架,這個框架在JS交互上為我們提供了很大幫助,可以在html界面上調(diào)用OC方法并傳參,也可以在OC上調(diào)用JS方法并傳參。關于JavaScriptCore的詳細介紹和注意事項,請大家自行google之。這里我們只是簡單介紹一下用法,很淺顯,請大家不要見怪。
使用之前,我們需要導入JavaScriptCore.framework這個框架

context上下文,context上下文直接設置和調(diào)用
1、 OBJC調(diào)用JS

OBJC調(diào)用JS之前,需要獲取一下JS的上下文,

self.context = [self.homeWebView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
        NSLog(@"self.contex=%@",self.context);

獲取到這個上下文之后,我們就可以對JS的執(zhí)行環(huán)境做處理了,假如我們需要調(diào)用JS中已經(jīng)實現(xiàn)過的函數(shù),我們可以直接調(diào)用相應的函數(shù)就行:

- (void)callJSBtnAction:(id)sender {
    NSLog(@"開始調(diào)用JS函數(shù),有返回值");
    //第一種
//    NSString *returnStr = [self.homeWebView stringByEvaluatingJavaScriptFromString:@"objcCallJS()"];
//    NSLog(@"返回值為:%@",returnStr);
    
    //第二種
//    NSString *js = @"objcCallJS()";
//    JSValue *value = [self.context evaluateScript:js];
//    NSLog(@"%@",[value toString]);
    
    //第三種
    JSValue *valueFuc = self.context[@"objcCallJS"];
    JSValue *value = [valueFuc callWithArguments:nil];
    NSLog(@"%@",[value toString]);
}

以上代碼中我們展示了三種不同的調(diào)用方式,特別說明的是第三種,首先我們從上下文中獲取要執(zhí)行的函數(shù),并把它保存在一個JSValue變量中JSValue *valueFuc = self.context[@"objcCallJS"];,然后對這個變量調(diào)用其函數(shù)- (JSValue *)callWithArguments:(NSArray *)arguments;傳入?yún)?shù)。
我們可以在JS中這樣使用:
因為我們傳入的參數(shù)為nil,所以我們在JS中沒有獲取參數(shù),代碼demo中我們提供了獲取參數(shù)的版本,具體的可以參照代碼demo,

function objcCallJS() {
        var data = 'ljt'
        alert('來自objc的調(diào)用,有返回值:' + data);
        return data
 }
2、 JS調(diào)用OBJC

首先我們在OBJC中,增加JS可以執(zhí)行的代碼,怎么添加呢?還是需要用到上面所提到的上下文context。
加入我們需要實現(xiàn)一個可以接收參數(shù)的函數(shù)名為JSCallObjcParam,該怎么寫呢?

//有參數(shù)
    __weak typeof(self) weakSelf = self;
    self.context[@"JSCallObjcParam"] = (id)^(NSString *param1, NSString *param2) {
        NSLog(@"有參,無返回值");
        NSString *message = [NSString stringWithFormat:@"%@:%@",param1,param2];
        UIAlertController *alterVC = [UIAlertController alertControllerWithTitle:@"objc彈框" message:message preferredStyle:UIAlertControllerStyleAlert];
        UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
        [alterVC addAction:okAction];
        [weakSelf presentViewController:alterVC animated:YES completion:nil];
    };

這樣我們就實現(xiàn)了一個可以在JS中調(diào)用的函數(shù)JSCallObjcParam,這個函數(shù)就收兩個參數(shù)。
我們在JS中這樣調(diào)用:

//調(diào)用OBJC
    function callObjcParam(param1,param2) {
        var data = JSCallObjcParam('ljt','ths')
        alert('來自objc(callObjcParam)的返回值:' + data);
    }

這么兩步我們就實現(xiàn)了iOS和JS的相互調(diào)用。</br>
demo中我們分別實現(xiàn)了相互調(diào)用的傳參和不傳參,有返回值和沒有返回值的情況,具體的可以看demo代碼。

JSExport協(xié)議,通過JSExport協(xié)議設置和調(diào)用

JSExport是一個協(xié)議,我們可以自定義一個協(xié)議,繼承此協(xié)議,在協(xié)議中聲明可以在JS中使用的API函數(shù)。

首先我們實現(xiàn)一個這么樣的協(xié)議ObjcJSDelegate

@protocol ObjcJSDelegate <JSExport>

//有參數(shù)
- (void)JSCallObjcParam:(NSString *)param1 with:(NSString *)param2;
//無參數(shù)
- (void)JSCallObjc;  
//有返回值
- (NSString *)JSCallObjcReturn;
@end

<strong>在這里我們需要知道的是,這個協(xié)議中所聲明的接口是供JS調(diào)用的,也就是說JS代碼調(diào)用OBJC的相關接口,可以放在這個協(xié)議中。而OBJC調(diào)用JS還是通過上下文來直接調(diào)用的</strong>

其次,我們需要一個實現(xiàn)了這個代理的類,我們就直接實現(xiàn)在@interface JSExportViewController ()<ObjcJSDelegate>UIWebView所在的ViewController中:

#pragma mark ObjcJSDelegate

- (void)JSCallObjcParam:(NSString *)param1 with:(NSString *)param2 {
    NSLog(@"有參,無返回值");
    NSString *message = [NSString stringWithFormat:@"%@:%@",param1,param2];
    UIAlertController *alterVC = [UIAlertController alertControllerWithTitle:@"objc彈框" message:message preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
    [alterVC addAction:okAction];
    [self presentViewController:alterVC animated:YES completion:nil];
}

- (void)JSCallObjc {
    NSLog(@"無參,無返回值");
}

- (NSString *)JSCallObjcReturn {
    NSLog(@"無參,有返回值");
    return @"ljt";
}

然后我們需要讓JS知道如何調(diào)用我們所需要的函數(shù),這個時候同樣需要前面一直提到的上下文:

self.context[@"OBJCFuc"] = self;

如此,我們就可以在JS中通過OBJCFuc(OBJCFuc.接口名字)來調(diào)用我們之前協(xié)議實現(xiàn)的相關接口了:
JS調(diào)用

//調(diào)用OBJC
    function callObjcParam(param1,param2) {
        var data = OBJCFuc.JSCallObjcParamWith('ljt','ths')
        alert('來自objc(callObjcParam)的返回值:' + data);
    }
    function callObjc() {
        var data = OBJCFuc.JSCallObjc()
        alert('來自objc(callObjc)的返回值:' + data);
    }
    
    function callObjcReturn() {
        var data = OBJCFuc.JSCallObjcReturn()
        alert('來自objc(callObjcReturn)的返回值:' + data);
    }

這樣就實現(xiàn)了JS調(diào)用OJBC。


這篇文章,只是簡單的介紹了iOS和JS的交互的幾種方法,很粗淺,很簡單,但也許很實用,最后附上代碼demo

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

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,078評論 0 9
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,893評論 33 466
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,234評論 25 708
  • 把瑜伽拉伸動作校正了下,每天若有其事、學啥就要學出個樣子,本著這顆心,開始吧 絕不拖延、這是最近潛移默化顯現(xiàn)出來的...
    Molly喵小北閱讀 244評論 0 3
  • 人生最大的修養(yǎng)是包容 人生最大的修養(yǎng)是包容。它既不是懦弱也不是忍讓,而是察人之難,補人之短,揚人之長,諒人之過,而...
    xcy無名閱讀 262評論 0 0

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