JS和原生

1 通訊原理

除了JavaScriptCore,JS調用原生只能異步處理返回值css

1.1 URL攔截(JS調用原生) + 解析JS(原生調用JS)

1.1.1 基於生命週期的攔截

基於URL協議,JS發送請求,在WebView生命週期方法中攔截到如myURLProtocol://。 這種方法對於UIWebView和WKWebView通用。java

  • 1 攔截:針對JS調用原生。 若是JS須要返回值,須要在參數里加上回調函數(或根據協議,約定好怎麼回調),而後經過原生調用JS的方式,把返回值傳回去。
//WKWebView
#pragma mark - WKNavigationDelegate
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
    NSURL *URL = navigationAction.request.URL;
    NSString *scheme = [URL scheme];
    if ([scheme isEqualToString:@"myURLProtocol"]) {
        [self handleCustomAction:URL];    //本身解析協議
        decisionHandler(WKNavigationActionPolicyCancel);
        return;
    }
    decisionHandler(WKNavigationActionPolicyAllow);
}

//UIWebView
#pragma mark - UIWebViewDelegate
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    NSURL *URL = request.URL;
    NSString *scheme = [URL scheme];
    if ([scheme isEqualToString:@"myURLProtocol"]) {
        [self handleCustomAction:URL];    //本身解析協議
        return NO;
    }
    return YES;
}
複製代碼
  • 2 解析:針對原生調用JS
//WKWebView
[self.webView evaluateJavaScript:jsStr completionHandler:^(id _Nullable result, NSError * _Nullable error) {
        NSLog(@"%@----%@",result, error);
    }];

//UIWebView
[_webView stringByEvaluatingJavaScriptFromString:jsStr];    //須要本身另外處理回調
複製代碼

iOS下JS與OC互相調用(一)--UIWebView 攔截URL iOS下JS與OC互相調用(二)--WKWebView 攔截URLweb

###1.1.2 基於NSURLProtocol的攔截瀏覽器

  1. 繼承NSURLProtocol,並註冊子類。
  2. 重寫+ (BOOL)canInitWithRequest:(NSURLRequest *)request等方法
  3. 對於WK,沒有官方支持,下文2方案也不完善。

iOS 開發中使用 NSURLProtocol 攔截 HTTP 請求bash

iOS WKWebView (NSURLProtocol)攔截js、css,圖片資源app

1.2 基於JavaScriptCore,只適用UIWebView

見第2節異步

1.3 基於MessageHandle,只適用WKWebView

見第3節ide

2 基於JavaScriptCore

2.1 JavaScriptCore的構成

JS的引擎,原生和JS互相訪問函數

最先是WebKit中解釋執行JS,做用至關於Chrome的V8post

  1. JSVirtualMachine 每一個虛擬機對應原生的一個線程。 每一個虛擬機有本身的GC,多個虛擬機之間的內存沒法共享。
  2. JSContext 表明JS的上下文環境(瀏覽器中的一個tab?) 能夠從UIWebView中獲取,也能夠從本地JS文件中讀取。
  3. JSValue 原生調用JS的關鍵, 經過Context獲取到JSValue,能夠表明js的一個變量、函數
  4. JSExport協議 JS調用原生的關鍵,原生經過JSExport協議給JS提供接口。

2.2 實例

2.2.1 原生訪問JS的變量,經過context拿到變量i

JSContext *context = [[JSContext alloc] init];
// 解析執行 JavaScript 腳本
[context evaluateScript:@"var i = 4 + 8"];
// 轉換 i 變量爲原生對象
NSNumber *number = [context[@"i"] toNumber];
NSLog(@"var i is %@, number is %@",context[@"i"], number); 
複製代碼

2.2.2 原生訪問JS的函數,將JSValue映射到context中的一個函數。

// 解析執行 JavaScript 腳本
[context evaluateScript:@"function addition(x, y) { return x + y}"];
// 得到 addition 函數
JSValue *addition = context[@"addition"];
// 傳入參數執行 addition 函數
JSValue *resultValue = [addition callWithArguments:@[@(4), @(8)]];
// 將 addition 函數執行的結果轉成原生 NSNumber 來使用。
NSLog(@"function is %@; reslutValue is %@",addition, [resultValue toNumber]);
複製代碼

2.2.3 JS訪問原生,經過JSExport協議

@protocol YYYJSExports <JSExport>
-(void)record:(NSString *)msg;
@end

@interface YYYUIWebViewJSBridge()<YYYJSExports>
@property (nonatomic, strong) JSContext *jsContext;
@end

@implementation YYYUIWebViewJSBridge

+ (void)registWebView:(UIWebView *_Nonnull)webView withId:(NSString *_Nonnull)webViewId {
    if(!webView || !webViewId) return;
    
    SHWUIWebViewJSBridge *bridge = [SHWUIWebViewJSBridge new];
    bridge.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    bridge.jsContext[@"shw_analytics"] = bridge;
    [bridgeContainer setObject:webView forKey:webViewId];
}

+ (void)deRegistWebViewWithId:(NSString *_Nonnull)webViewId {
    if(!webViewId) return;
    
    [bridgeContainer removeObjectForKey:webViewId];
}

- (void)record:(NSString *)msg {
    SHWEvent *event = [SHWEvent initEventWithId:_SW_ANALYTICS_EVENTID_WEBVIEW args:@{__SW_KEY_WebView: msg}];
    [SHWMsgQueue logEvent:event];
}
複製代碼

3 基於MessageHandler

MessageHandler,做用相似於JSExport,爲JS調用原生提供接口。 但原生調用JS,不能像JSContext那樣直接調用,而是經過經過evaluateJS。

iOS下JS與OC互相調用(三)--MessageHandler

3.1 原生調用JS

NSString *jsStr2 = @"window.ctuapp_share_img";
    [self.webView evaluateJavaScript:jsStr2 completionHandler:^(id _Nullable result, NSError * _Nullable error) {
        NSLog(@"%@----%@",result, error);
    }];
複製代碼

3.2 JS調用原生

//viewWillAppear:
[self.webView.configuration.userContentController addScriptMessageHandler:self name:@"ScanAction"];

//viewWillDisappear:
[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"ScanAction"];

#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
// message.body -- Allowed types are NSNumber, NSString, NSDate, NSArray,NSDictionary, and NSNull.
    NSLog(@"body:%@",message.body);
    if ([message.name isEqualToString:@"ScanAction"]) {
        NSLog(@"掃一掃");
    } else if ([message.name isEqualToString:@"Location"]) {
        [self getLocation];
    } else if ([message.name isEqualToString:@"Share"]) {
        [self shareWithParams:message.body];
    }
}
複製代碼
相關文章
相關標籤/搜索