iOS 與 Web 互調(diào)

前提

蘋果推出JSCore 以前捉片,iOS 調(diào)用JS 只能通過WebView 執(zhí)行JSString 來實(shí)現(xiàn)桃熄。而WebView 沒法直接調(diào)用iOS帮掉,只能觸發(fā)特定鏈接,讓iOS在WebView代理方法中捕獲到這特定鏈接矾湃,從而執(zhí)行相應(yīng)操作,間接實(shí)現(xiàn)WebView 調(diào) iOS堕澄。

原理

// iOS 調(diào) JS
[_webView stringByEvaluatingJavaScriptFromString:jsString];

// JS 調(diào) iOS
1:
web 將href 改為特定值
2:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    NSURL *url = [request URL];
    if  (url == 特定值) {call method}
}

iOS7之后蘋果推出JSCore邀跃,通過獲取web上下文環(huán)境,實(shí)現(xiàn)了iOS可以直接調(diào)JS方法蛙紫,同時(shí)iOS 也能將block 賦值給JS的方法坞嘀,實(shí)現(xiàn)了JS調(diào)用iOS并傳值。

// 獲取JS上下文
 JSContext *jsContext;
-(void)webViewDidFinishLoad:(UIWebView *)webView {
    jsContext = [_webview valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
}

// iOS 調(diào) JS
JSValue *funcValue = jsContext[@"insertText"];
[funcValue callWithArguments:@[@"hello!!!!"]];

//JS 調(diào) iOS
jsContext[@"callNative"] = ^(NSString*str){
 NSLog(str);
 };

三方庫WebViewJavascriptBridge

當(dāng)然了不用三方庫惊来,自己也是能實(shí)現(xiàn)丽涩,之所有在這介紹這個(gè)三方庫,還是覺得這個(gè)庫設(shè)計(jì)還是不錯(cuò)裁蚁,值得學(xué)習(xí)矢渊。

結(jié)構(gòu):

iOS端用法:

  • iOS 端與 web端統(tǒng)一好handler 名。
  • 初始化bridge
    _bridge = [WebViewJavascriptBridge bridgeForWebView:_webView];
  • iOS call Web枉证,iOS 端要做的
  // iOS 端直接callHandler
    [_bridge callHandler:@"JSToDo" data:@{}];
  • Web call iOS 矮男,iOS 端要做的
// 注冊(cè)handler 等待被Web調(diào)用
    [_bridge registerHandler:@"OCToDo" handler:^(id data, WVJBResponseCallback responseCallback) {
        NSString *str = [NSString stringWithFormat:@"%@",data];
        UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"iOS alert" message:str preferredStyle:UIAlertControllerStyleAlert];
        UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"ok" style:UIAlertActionStyleDefault handler:nil];
        [alert addAction:okAction];
       [self presentViewController:alert animated:false completion:nil];

        responseCallback(@"iOS receive data");
    }];

用法對(duì)法iOS 端來說還是蠻簡單的!當(dāng)然這些僅僅靠iOS 端還不夠室谚,需要web 端配合毡鉴,待分析了實(shí)現(xiàn)原理后再看web 端的工作。

關(guān)系秒赤、作用:

前面說過JSCore 以前 iOS調(diào)web 只能用webView 執(zhí)行JSString猪瞬, web 調(diào) iOS 只能主動(dòng)觸發(fā)特定url 讓iOS 端監(jiān)聽到,實(shí)現(xiàn)間接調(diào)用入篮。那這個(gè)三方庫的實(shí)現(xiàn)基礎(chǔ)正是如此陈瘦。只不過在代碼層面通過“觀察者模式”加以封裝。

  • Class: WebViewJavascriptBridge
    1潮售、 在初始化時(shí)成為webView 的真實(shí)代理
// 實(shí)例化
  + (instancetype)bridgeForWebView:(WVJB_WEBVIEW_TYPE*)webView {
      WebViewJavascriptBridge* bridge = [[self alloc] init];
      [bridge _platformSpecificSetup:webView];
      return bridge;
   }
// 成為代理痊项、初始化WebVeiwJavascriptBridgeBase
   - (void) _platformSpecificSetup:(WVJB_WEBVIEW_TYPE*)webView {
      _webView = webView;
      _webView.delegate = self;
      _base = [[WebViewJavascriptBridgeBase alloc] init];
      _base.delegate = self;
}
// 實(shí)現(xiàn)WebViewDelegate方法
 ...

2、暴露接口酥诽, 提供callHandler鞍泉、registerHandler 方法

    - (void)registerHandler:(NSString*)handlerName handler:(WVJBHandler)handler;
    - (void)callHandler:(NSString*)handlerName;
    - (void)callHandler:(NSString*)handlerName data:(id)data;
    - (void)callHandler:(NSString*)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback;

3、監(jiān)聽Url變化

  - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    if (webView != _webView) { return YES; }
    NSURL *url = [request URL];
    __strong WVJB_WEBVIEW_DELEGATE_TYPE* strongDelegate = _webViewDelegate;
    if ([_base isCorrectProcotocolScheme:url]) {
        if ([_base isBridgeLoadedURL:url]) {// wvjbscheme://__BRIDGE_LOADED__
            [_base injectJavascriptFile]; // 重點(diǎn):注入JSBridge
        } else if ([_base isQueueMessageURL:url]) {
            NSString *messageQueueString = [self _evaluateJavascript:[_base  webViewJavascriptFetchQueyCommand]]; //從Web 頁面獲取JS 數(shù)據(jù)
            [_base flushMessageQueue:messageQueueString];  // 數(shù)據(jù)處理
        } else {
            [_base logUnkownMessage:url];
        }
        return NO;
    } else if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {
        return [strongDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
    } else {
        return YES;
    }
}

主要就是提供接口肮帐、監(jiān)聽變化咖驮,具體實(shí)現(xiàn)都是交給WebViewJavascriptBridgeBase。

  • Class: WebViewJavascriptBridgeBase
    給WebViewJavascript 類充當(dāng)業(yè)務(wù)層,實(shí)現(xiàn)CallHandler游沿、registerHandler饰抒。通過操控WebView 執(zhí)行JSString以及監(jiān)聽WebView URL 變化 實(shí)現(xiàn)iOS 與 Web交互。
    1诀黍、 RegisterHandler
  // 將handlerName 保存起來
    _base.messageHandlers[handlerName] = [handler copy];

2袋坑、CallHandler
message字段處理(需要回調(diào)時(shí)將responseCallback按唯一callbackId保存)—>message 轉(zhuǎn)json字符串—>WebView 執(zhí)行JSString

3、消息處理
messageString 轉(zhuǎn)數(shù)組
遍歷
處理:取每條消息眯勾,看是否有responseId枣宫,有則是iOS端調(diào)Web端之后所需要的回調(diào)。沒有則就是Web主動(dòng)調(diào)iOS所發(fā)送數(shù)據(jù)吃环。

        if (responseId) {
            WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
            responseCallback(message[@"responseData"]);
            [self.responseCallbacks removeObjectForKey:responseId];
        } else {
            WVJBResponseCallback responseCallback = NULL;
            NSString* callbackId = message[@"callbackId"];
            if (callbackId) {
                responseCallback = ^(id responseData) {
                    if (responseData == nil) {
                        responseData = [NSNull null];
                    }
                    WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
                    [self _queueMessage:msg];
                };
            } else {
                responseCallback = ^(id ignoreResponseData) {
                    // Do nothing
                };
            }
            WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];
            if (!handler) {
                NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);
                continue;
            }
            handler(message[@"data"], responseCallback);
        }
  • Class: WebViewJavascriptBridge_JS
    一段字符串也颤,同時(shí)也是真正的bridge, 通過WebView 執(zhí)行JSString 將 全局變量WebViewJavascriptBridge放在Web 頁面的JS 中郁轻。
       window.WebViewJavascriptBridge = {
         registerHandler: registerHandler,
         callHandler: callHandler,
         disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,
         _fetchQueue: _fetchQueue,
         _handleMessageFromObjC: _handleMessageFromObjC
       };
``
不管Web頁面怎么變翅娶,JSBridge 是不變的,僅僅提供幾個(gè)交互的方法就夠了好唯,所以這一塊被提取出來竭沫,以string 的形式存放在三方庫的文件 WebViewJavascriptBridge_JS.m中,需要用時(shí)注入頁面就行骑篙。這也是這個(gè)三方庫架構(gòu)設(shè)計(jì)很合理的地方蜕提。
- Class: WKWebViewJavascriptBridge
跟WebViewJavascriptBridge功能類似,僅僅為WKWebView 做些特殊處理靶端。

### Web 端要做的
- 注入bridge
  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)
  }
  當(dāng)然web端沒有必要自己保存JSBridge谎势,只是通過 “WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__'; ” 來觸發(fā)iOS 端將JSBridge 注入頁面。
- 如果需要被iOS 調(diào)用則registerHandler
 bridge.registerHandler('JSToDo', function(data, responseCallback) {
      var a = document.createElement("span");
      a.innerText = "hello !!!!";
      document.documentElement.appendChild(a);
          responseCallback(data)
   })
- 如果需要調(diào)iOS 則callHandler
window.WebViewJavascriptBridge.callHandler('OCToDo',{name:'carson'}, function responseCallback(responseData) {
    console.log("JS received response:", responseData)

})

前面說了web 不能主動(dòng)調(diào)iOS, 那web callHandler 時(shí)也是只能觸發(fā)一個(gè)特定url 請(qǐng)求杨名,同時(shí)將數(shù)據(jù)放在頁面上脏榆,iOS監(jiān)聽到url 則去獲取頁面數(shù)據(jù)。實(shí)現(xiàn)Web  調(diào)iOS镣煮。

##總結(jié)
其實(shí)重點(diǎn)很少姐霍,也提了很多次鄙麦,也就是WebView 執(zhí)行JSString 實(shí)現(xiàn)調(diào)web, web 觸發(fā)特定URL 讓原生監(jiān)聽到典唇,執(zhí)行相應(yīng)操作,實(shí)現(xiàn)web調(diào)iOS 胯府。當(dāng)然JSCore 出現(xiàn)之后就不再是這個(gè)原理了介衔,比這直接很多,最前面的原理也提到了骂因。
這個(gè)庫也還是有很多值得學(xué)習(xí)的地方炎咖,包括觀察者模式,web和原生都只管注冊(cè)和調(diào)用就夠了。還有將JSBridge 的抽象出來乘盼,放在三方庫中升熊,簡化了很多web代碼。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末绸栅,一起剝皮案震驚了整個(gè)濱河市级野,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌粹胯,老刑警劉巖蓖柔,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異风纠,居然都是意外死亡况鸣,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門竹观,熙熙樓的掌柜王于貴愁眉苦臉地迎上來镐捧,“玉大人,你說我怎么就攤上這事臭增》吖溃” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵速址,是天一觀的道長玩焰。 經(jīng)常有香客問我,道長芍锚,這世上最難降的妖魔是什么昔园? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮并炮,結(jié)果婚禮上默刚,老公的妹妹穿的比我還像新娘。我一直安慰自己逃魄,他們只是感情好荤西,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著伍俘,像睡著了一般邪锌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上癌瘾,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天觅丰,我揣著相機(jī)與錄音,去河邊找鬼妨退。 笑死妇萄,一個(gè)胖子當(dāng)著我的面吹牛蜕企,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播冠句,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼轻掩,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了懦底?” 一聲冷哼從身側(cè)響起放典,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎基茵,沒想到半個(gè)月后奋构,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拱层,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年弥臼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片根灯。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡径缅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出烙肺,到底是詐尸還是另有隱情纳猪,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布桃笙,位于F島的核電站氏堤,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏搏明。R本人自食惡果不足惜鼠锈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望星著。 院中可真熱鬧购笆,春花似錦、人聲如沸虚循。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽横缔。三九已至铺遂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間剪廉,已是汗流浹背娃循。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留斗蒋,地道東北人捌斧。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像泉沾,于是被迫代替她去往敵國和親捞蚂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容