iOS H 5容器のいくつかの探究(一):UIWebViewとWKWebViewの比較と選択

12823 ワード

一、Native開発においてなぜH 5容器が必要なのか
Nativeが開発したオリジナルアプリケーションは、携帯電話のオペレーティングシステムメーカー(現在は主にアップルのiOSとgoogleのAndroid)が外部に提供する標準化された開発モデルであり、native開発に標準化の実現と最適化案を提供している.しかし、Appのリリースサイクルが長く、製品の更新リズムに追いつかないなどの硬い傷があります.柔軟性が悪く、大きな案の変更があれば、版を出す必要があります.バグがある場合は、現在のバージョンでは修復の難易度が高い(iOSのJSPochスキームとAndroidのDex修復スキーム).異なるプラットフォームに基づいて異なるコードを書く必要があります.iOSは主にobject_です.cとswift,androidはJavaである.
H 5を主な開発モデルとするWeb Appの柔軟性は比較的強く、オペレーティングシステムのh 5コンテナを1つのキャリアとして利用し、urlリンクを対外的に提供しているが、このurlリンクに対応する内容はリアルタイムでサービス側で修正することができ、柔軟性が強く、Nativeリリースサイクルによる時間コストを回避している.しかしh 5は柔軟だが、彼にも自分の硬傷がある.毎回完全なUIデータ(html,css,js)をダウンロードする必要があり、弱いネットユーザーの体験が悪く、トラフィック消費が大きい.システムファイルシステム、ハードウェアリソースなどを呼び出すことができません.
Native AppとWeb Appには彼らの優位性と劣勢がある.私たちも棒で誰がいいか誰が悪いかを殺すことはできません.通常の経験では、より安定したビジネスに対して、ユーザー体験に対する要求が高い場合は、Native開発を選択することができます.一部の業務の変更が比較的速く、絶えず試水する過程にあり、ファイルシステムとハードウェア呼び出しを呼び出す業務に関与しない場合は、h 5開発を選択することができます.したがって、appではNativeコードとh 5コードを同時にサポートする必要があります.これも我々の見出しで述べたNative開発にH 5容器を必要とする必要性である.
iOSに存在するh 5容器には主にUIWebViewとWKWebViewが含まれており、それぞれの使い方と優劣を説明します.
二、UIWebViewの基本的な使い方
2.1、ホームページをロードする
    UIWebView *webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
    webView.delegate = self;
    [self.view addSubview:webView];
    //    
    NSURL *url = [[NSURL alloc] initWithString:@"http://www.taobao.com"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    [webView loadRequest:request];

2.2、UIWebViewDelegateのいくつかのよく使われる代理方法
//         ,    YES,        (StartLoad,FinishLoad)。    NO,         。
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
//      
- (void)webViewDidStartLoad:(UIWebView *)webView;
//    
- (void)webViewDidFinishLoad:(UIWebView *)webView;
//    
- (void)webView:(UIWebView *)webView didFailLoadWithError:(nullable NSError *)error;

2.3.NativeがJSを呼び出す方法
たとえば、ロードされたHTMLファイルには、次のjsコードがあります.

function hello(){
    alert("  !");
}

function helloWithName(name){
    alert(name + ",  !");
}

- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;関数を呼び出してjs呼び出しを行うことができます.
[webView stringByEvaluatingJavaScriptFromString:@"hello()"];
[webView stringByEvaluatingJavaScriptFromString:@"helloWithName('jack')"];

jsコードは、jsファイルに予約する必要はありません.次のように、コード内で文字列の形式で呼び出すこともできます.
    //   js  
    NSString *jsString = @"function sayHello(){ \
                                alert('jack11')   \
                            }                   \
                           sayHello()";
    [_webView stringByEvaluatingJavaScriptFromString:jsString];
    
    NSString *jsString = @" var p = document.createElement('p'); \
                            p.innerText = 'New Line';            \
                            document.body.appendChild(p);        \
    ";
    [_webView stringByEvaluatingJavaScriptFromString:jsString];

2.4、JSでNaitveを呼び出す方法
具体的には、jsにnativeにメソッド呼び出しを通知させ、jsに特別なリクエストを生成させることができます.Nativeコードをブロックすることができ、そうでないとユーザーが気づくことができます.業界の一般的な実装案は、Webページに非表示のiframeをロードして機能を実現することである.iframeのsrcを特殊なURLとして指定することにより、- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;スキームにおけるブロック処理が実現される.対応するjs呼び出しコードは次のとおりです.
    function loadURL(url) {
        var iFrame;
        iFrame = document.createElement("iframe");
        iFrame.setAttribute("src", url);
        iFrame.setAttribute("style", "display:none;");
        iFrame.setAttribute("height", "0px");
        iFrame.setAttribute("width", "0px");
        iFrame.setAttribute("frameborder", "0");
        document.body.appendChild(iFrame);
        //        iFrame    ,     dom    
        iFrame.parentNode.removeChild(iFrame);
        iFrame = null;
    }

たとえばjsコードで、2つのjsメソッドを呼び出します.
    function iOS_alert() {//        
        loadURL("alert://abc");
    }
    function call() {//  js         
        loadURL("tel://17715022071");
    }

以上のメソッドをトリガーすると、webviewのエージェントメソッドにブロックされます.
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
    NSURL * url = [request URL];
    if ([[url scheme] isEqualToString:@"alert"]) {//    ,        
        UIAlertView * alertView = [[UIAlertView alloc] initWithTitle:@"test" message:[url host] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
        [alertView show];
        return NO;
    }else if([[url scheme] isEqualToString:@"tel"]){//        
        BOOL result = [[UIApplication sharedApplication] openURL:url];
        if (!result) {
            NSLog(@"          ");
        } else {
            NSLog(@"    ");
        }
        return NO;
    }
    
    return YES;
}

これによりjsにnativeの呼び出しを行うことができます.
三、WKWebViewの基本的な使い方
3.1、ホームページのロード
    WKWebView *webView = [[WKWebView alloc] initWithFrame:[UIScreen mainScreen].bounds];
    NSURL *url = [NSURL URLWithString:@"http://www.taobao.com"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    [webView loadRequest:request];
    [self.view addSubview:webView];

3.2、いくつかのよく使う代理方法
/**
 *    webView、navigationAction                  ,      HTTP    ,     User-Agent,Accept,refer
 *         ,         
 *  @param webView
 *  @param navigationAction
 *  @param decisionHandler
 */
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
    decisionHandler(WKNavigationActionPolicyAllow);
}

/**
 *                       ,  response    ,                。
 *        ,         
 *  @param webView
 *  @param navigationResponse
 *  @param decisionHandler
 */
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
    decisionHandler(WKNavigationResponsePolicyAllow);
}

/**
 *        。   UIWebViewDelegate: - webView:shouldStartLoadWithRequest:navigationType
 *
 *  @param webView
 *  @param navigation
 */
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation{
}

/**
 *          redirect   
 *               
 *  @param webView
 *  @param navigation
 */
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation{
    
}

- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error{
    
}

/**
 *        .    UIWebViewDelegate: - webViewDidStartLoad:
 *
 *  @param webView
 *  @param navigation
 */
- (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation{
    
}

/**
 *        。    UIWebViewDelegate: - webViewDidFinishLoad:
 *
 *  @param webView
 *  @param navigation
 */
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation{
    
}

/**
 *        。    UIWebViewDelegate: - webView:didFailLoadWithError:
 *
 *  @param webView
 *  @param navigation
 *  @param error      
 */
- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error{
    
}

- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView NS_AVAILABLE(10_11, 9_0){
    
}

/*
     WKUIDelegate       ,         ,              js alert、confirm、prompt  ,               ,         native    ,    WKWebView ,HTML  alert、confirm、prompt             ,     ios native      
 */
#pragma mark - WKUIDelegate

- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
    UIAlertController *alertView = [UIAlertController alertControllerWithTitle:@"h5Container" message:message preferredStyle:UIAlertControllerStyleAlert];
//    [alertView addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
//        textField.textColor = [UIColor redColor];
//    }];
    [alertView addAction:[UIAlertAction actionWithTitle:@"    " style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler();
    }]];
    [self presentViewController:alertView animated:YES completion:nil];
}

明らかにWKWebViewの代理法はUIWebViewよりも粒子度が細い方法を提供する.開発者がより詳細な構成と処理を行うことができます.
3.3、NativeがJSを呼び出す方法
WKWebViewが提供するjsコードを呼び出す関数は、次のとおりです.
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ __nullable)(__nullable id, NSError * __nullable error))completionHandler;

たとえば、ロードされたHTMLファイルには、次のjsコードがあります.

function hello(){
    alert("  !");
}

function helloWithName(name){
    alert(name + ",  !");
}


jsの呼び出しには、次のコードを呼び出すことができます.
[_wkView evaluateJavaScript:@"hello()" completionHandler:^(id item, NSError * error) {
      
}];
    
 [_wkView evaluateJavaScript:@"helloWithName('jack')" completionHandler:^(id item, NSError *error) {
      
}];

UIWebViewと同様に、文字列形式でjs呼び出しを行うこともできます.
    NSString *jsString = @"function sayHello(){ \
                                    alert('jack11')   \
                                }                   \
                               sayHello()";
    [_wkView evaluateJavaScript:jsString completionHandler:^(id item, NSError *error) {
        
    }];
    
    jsString = @" var p = document.createElement('p'); \
    p.innerText = 'New Line';            \
    document.body.appendChild(p);        \
    ";
    [_wkView evaluateJavaScript:jsString completionHandler:^(id item, NSError *error) {
        
    }];

3.4、JSでNaitveを呼び出す方法
UIWebViewと非表示のifameをロードするほか、WKWebView自体はjs呼び出しnativeの仕様を提供しています.
WKWebViewを初期化するときにconfigパラメータを設定することができます.
//    
    //    
    WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
    //  UserContentController(  javaScript webView       )
    WKUserContentController *userContent = [[WKUserContentController alloc] init];
    //      ,  :self        WKScriptMessageHandler  ,       
    [userContent addScriptMessageHandler:self name:@"NativeMethod"];
    // UserContentController        
    config.userContentController = userContent;
    //          WKWebView
    _wkView = [[YXWKView alloc] initWithFrame:self.view.bounds configuration:config];
    NSURL *url = [NSURL URLWithString:@"http://localhost:8080/myDiary/index.html"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    [_wkView loadRequest:request];
    _wkView.UIDelegate = self;
    _wkView.navigationDelegate = self;
    [self.view addSubview:_wkView];

私たちはjsでNativeMethodというHandlerを通じてjsコードにnativeを呼び出すことができます.
例えばjsコードでは、新しい方法を追加しました.

    function invokeNativeMethod(){
        window.webkit.messageHandlers.NativeMethod.postMessage("    native   ");
    }


以上のメソッドをトリガするとnative以下のメソッドでブロック処理が行われる.
#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
    //          ,js  native     。      name body,        。
    NSString *messageName = message.name;
    if ([@"NativeMethod" isEqualToString:messageName]) {
        id messageBody = message.body;
        NSLog(@"%@",messageBody);
    }
}

四、UIWebViewとWKWebViewの比較と選択
WKWebViewは、アップルがWWDC 2014発表会でIOS 8を発表した際にWebKitを発表した際に使用した新型のH 5容器.UIWebViewと比較して、より高速なロード速度とパフォーマンス、より低いメモリ消費量を実現します.UIWebViewDelegateとUIWebViewを14クラス,3プロトコルに再構成し,開発者がより細かく構成できるようにした.
しかし、彼には最も致命的な欠陥がある.WKWebViewの要求がNSURLProtocolにキャプチャされないことだ.一方,我々のチームが開発したappにおけるH 5コンテナに対する最適化点は,主にNSURLProtocol技術を用いてH 5をオフラインパケットで処理し,H 5のピクチャとNativeのピクチャを共通にキャッシュする技術にある.この問題のため、現在、私たちのチームはUIWebViewの代わりにWKWebViewを使用していません.
五、連絡先
新浪微博githubトップページ
友達を増やして、一緒に交流することを歓迎します.