iOS-JSBrigde:JS和Native通訊實現的兩種方式

weixin_33727510發表於2017-03-27

現在純原生的APP是越來越少了,更多的都加入了Web混合開發(有UiWebView和iOS 8之後的WKWebView,後者在效能上優化了許多),甚至有些直接是WebAPP。JSPatch、weex以及ReactNative等熱更新技術也相當流行,雖然2017年3月份蘋果的一封郵件警告,讓許多使用者對這些熱更新技術有點害怕。
既然用到了web,就避免不了JS和OC語言的互相呼叫。
本文介紹自己專案中的js-oc互相呼叫實現方法。從一開始簡單的url攔截到最近的基於JavaScriptcore建立的JSbrige兩套方案。

URL攔截方式

JS呼叫OC:


- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    
    NSString * currentUrl = webView.request.URL.absoluteString;
    //判斷是否是單擊
    if (navigationType == UIWebViewNavigationTypeLinkClicked)
    {
        NSURL *url = [request URL];
        NSLog(@"當前url%@",url);
        NSString *urlStr = url.absoluteString;
        if ([[url scheme] isEqualToString:@"cloudsc6"]){
            if ([[url host] isEqualToString:@"PLAYVOICE"]) {
                NSLog(@"準備播放語音訊息");
            }
        }
            else {
                return YES;
            }
    }
    return YES;
}

這個實現方式事實上有很多侷限,首先引數的傳遞就不太友好,引數放在url中實現實在不是很優雅的實現方式。
而且,工作中實測,當JS實現事件的方式是Ajax非同步的時候,有些事件這個代理是攔截不到的。
當時解決的方案是使用了NSURLProtocol全域性去攔截url請求。如下

@implementation InnerResourcesURLProtocol
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
    BOOL can = NO;
    //NSLog(@"--->can handled Request? #%lu: URL = %@", (unsigned long)requestCount++, request);
  //  NSLog(@"%@",request);
    if([NSURLProtocol propertyForKey:CLOUDSEE_INNER_PROTOCOL_REQ_HANDLED_KEY inRequest:request]){
        can =  NO;
    }else {
        NSURL *url = [request URL];
        if(([[url scheme] isEqualToString:DEFAULT_SCHEMA])||([[url scheme]isEqualToString:@"th"])){
            can = YES;
        }
    }
    return can;
}
- (void) startLoading
{
    AppDelegate *dd = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    NSURL *url = [self.request URL];
    if ([[url scheme] isEqualToString:@"PLAYVOICE"]) {
    }
}

同樣地也有其侷限性。

OC呼叫JS

-(void)webViewDidFinishLoad:(UIWebView *)webView
{
    NSString *scriptResult = [self.webView stringByEvaluatingJavaScriptFromString:@"didWebViewScroll()"];
    if([scriptResult isEqualToString:@"NO"]) {
        self.webView.scrollView.bounces=NO;
    }
}

這個實現方式簡單,但是同樣也相當不友好。首先傳引數在didWebViewScroll()中的()實現,不僅對長度有限,而且如果引數有含有''會導致擷取錯誤程式碼。

而且這個方案下,有些事件安卓能擷取,iOS 不能擷取。有些事件iOS 能擷取,安卓不能擷取。跨平臺性也不好。

WebViewJavascriptBridge

網上有一個著名的庫,星星數已達8823。但是其底層實現原理還是基於URL攔截方式去實現。
再次證明星星數不能代表什麼,只能說其比較出名。

JavaScriptcore橋接實現

如果你的APP不需要支援iOS 7之前的版本,可以使用以下方案,我們公司現在用的就是這個方案。
蘋果在iOS 7中加入了JavaScriptCore框架。該框架讓Objective-C和JavaScript程式碼直接的互動變得更加的簡單方便。

橋接關鍵 和JS呼叫OC

-(void)webViewDidFinishLoad:(UIWebView *)webView{
    self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    //列印JS異常
    [self.jsContext setExceptionHandler:^(JSContext *context, JSValue *value) {
        NSLog(@"oc catches the exception: %@", value);
    }];
    //以下是橋接JS與OC的關鍵
    JSModel * model = [[JSModel alloc]init];
    model.controller = self;
    model.JSContext = self.jsContext;
    //以下是H5中呼叫的方法名稱
    NSString * key = @"DDJSBridge";
    [self.jsContext setObject:model forKeyedSubscript:key];
    JSValue * loadData = self.jsContext[@"loadData"];
    //以下是回撥JS方法,可以傳入封裝好的引數
    [loadData callWithArguments:@[self.jsonStr]];
    if (isLoadData) {
        [self firstIn];
    }
    isLoadData = NO;
    // 以下是執行JS方法
    // [self.jsContext evaluateScript:self.htmlCont];
}

OC呼叫JS

只要將JSModel繼承JSExport,再本地實現以下方法

-(void)call:(NSString *)methodName :(NSDictionary *)params :(JSValue *)callBack{
    NSDictionary * data = [params objectForKey:@"data"];
    if ([methodName isEqualToString:@"showShareButton"]) {
        NSString * iconsStrUrl = [data objectForKey:@"icon"];
        if (iconsStrUrl) {
            NSURL * iconUrl = [NSURL URLWithString:iconsStrUrl];
            NSData * imageData = [NSData dataWithContentsOfURL:iconUrl];
            UIImage *image = [UIImage imageWithData:imageData scale:3];
            UIBarButtonItem *rightBarButton = [[UIBarButtonItem alloc]
                                               initWithImage:image
                                               style:UIBarButtonItemStylePlain
                                               target:self
                                               action:@selector(rightClick)];
            self.controller.navigationItem.rightBarButtonItem = rightBarButton;
        }
}

至於跨平臺性,也比第一個方案要友好,安卓也有類似的實現方案。

第二個方案的DEMO已經上傳,可以下載
JSBrigeOC版本
JSBrigeSwift版本

JavascriptBridge實現內部原理

有一個百度團隊的部落格寫的很好,可以參考。
深入淺出 JavaScriptCore
淺談JavaScriptCore

阿里無限團隊介面實現規範

我們也是基本按照它的介面規範的
阿里無限團隊介面實現規範

Weex 是如何在 iOS 客戶端上跑起來的

Weex 是如何在 iOS 客戶端上跑起來的

相關文章