IOS-H5容器的一些探究:UIWebView和WKWebView的比較和選擇


一、Native開發中為什么需要H5容器

Native開發原生應用是手機操作系統廠商(目前主要是蘋果的iOS和google的Android)對外界提供的標准化的開發模式,他們對於native開發提供了一套標准化實現和優化方案。但是他們存在一些硬傷,比如App的發版周期偏長、有時無法跟上產品的更新節奏;靈活性差,如果有較大的方案變更,需要發版才能解決;如果存在bug,在當前版本修復的難度比較大(iOS的JSPatch方案和Android的Dex修復方案);需要根據不同的平台寫不同的代碼,iOS主要為object_c和swift,android為Java。

而作為H5為主要開發模式的Web App的靈活性就比較強,他利用操作系統中的h5容器作為一個承載,對外提供一個url鏈接,而該url鏈接對應的內容可以實時在服務端進行修改,靈活行很強,避免了Native發版周期帶來的時間成本。但是h5雖然靈活,但是他也有自己的硬傷。每次都需要下載完整的UI數據(html,css,js),弱網用戶體驗較差,流量消耗較大;無法調用系統文件系統,硬件資源等等;

Native App和Web App都有他們的優勢和劣勢。我們也不能一棍子拍死說誰好誰劣。通常的經驗是:對於一些比較穩當的業務,對用戶體驗要求較高的,我們可以選擇Native開發。而對於一些業務變更比較快、處在不斷試水的過程,而且不涉及調用文件系統和硬件調用的業務我們可以選擇h5開發。所以說,在一款app中我們需要同時支持Native代碼和h5代碼。這也是我們標題所說的Native開發中需要H5容器的必要性。

iOS存在的h5容器主要包括UIWebView和WKWebView,下面我們就分別來說說他們的用法和優劣。

二、UIWebView的基本用法

2.1、加載網頁

1
2
3
4
5
     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幾個常用的代理方法

1
- ( BOOL )webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType; //進行加載前的預判斷,如果返回YES,則會進入后續流程(StartLoad,FinishLoad)。如果返回NO,這不會進入后續流程。- (void)webViewDidStartLoad:(UIWebView *)webView;//開始加載網頁- (void)webViewDidFinishLoad:(UIWebView *)webView;//加載完成- (void)webView:(UIWebView *)webView didFailLoadWithError:(nullable NSError *)error;//加載失敗

2.3、Native調用JS中的方法

比如我們在加載的HTML文件中有如下js代碼:

1
2
3
4
5
6
<script type= "text javascript" = "" >function hello(){
     alert( "你好!" );
}function helloWithName(name){
     alert(name +  ",你好!" );
}
</script type="text>

我們可以調用- (nullable NSString )stringByEvaluatingJavaScriptFromString:(NSString )script;函數進行js調用。

1
[webView stringByEvaluatingJavaScriptFromString:@ "hello()" ];[webView stringByEvaluatingJavaScriptFromString:@ "helloWithName('jack')" ];

js代碼不一定要在js文件中預留,也可以在代碼中通過字符串的形式進行調用,比如下面:

1
2
3
4
     //自定義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代碼可以攔截到,而且不然用戶察覺。業界一般的實現方案是在網頁中加載一個隱藏的iframe來實現該功能。通過將iframe的src指定為一個特殊的URL,實現在- (BOOL)webView:(UIWebView )webView shouldStartLoadWithRequest:(NSURLRequest )request navigationType:(UIWebViewNavigationType)navigationType;方案中進行攔截處理。對應的js調用代碼如下:

1
2
3
4
5
6
7
8
9
10
     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代碼中,調用一下兩個js方法:

1
2
3
4
5
     function iOS_alert() { //調用自定義對話框
         loadURL( "alert://abc" );
     }    function call() { //  js中進行撥打電話處理
         loadURL( "tel://17715022071" );
     }

當你觸發以上方法的時候,就會進入webview的代理方法中進行攔截。

1
2
3
4
5
6
7
8
9
- ( 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、加載網頁

1
2
3
     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、幾個常用的代理方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/**
  *  根據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代碼的函數是:

1
- ( void )evaluateJavaScript:(NSString *)javaScriptString completionHandler:( void  (^ __nullable)(__nullable id, NSError * __nullable error))completionHandler;

比如我們在加載的HTML文件中有如下js代碼:

1
2
3
4
5
6
<script type= "text javascript" = "" >function hello(){
     alert( "你好!" );
}function helloWithName(name){
     alert(name +  ",你好!" );
}
</script type="text>

我們可以調用如下代碼進行js的調用:

1
2
3
4
5
6
7
[_wkView evaluateJavaScript:@ "hello()"  completionHandler:^(id item, NSError * error) {
 
}];
 
  [_wkView evaluateJavaScript:@ "helloWithName('jack')"  completionHandler:^(id item, NSError *error) {
 
}];

同UIWebView一樣,我們也可以通過字符串的形式進行js調用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
     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參數。

1
2
3
4
5
6
7
8
9
10
11
//高端配置
     //創建配置
     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代碼中,我新增了一個方法

1
2
3
<script type= "text javascript" = "" >    function invokeNativeMethod(){        window.webkit.messageHandlers.NativeMethod.postMessage( "我要調用native的方法" );
     }
</script type="text>

觸發以上方法的時候,會在native以下方法中進行攔截處理。

1
2
3
4
#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是蘋果在WWDC2014發布會中發布IOS8的時候公布WebKit時候使用的新型的H5容器。它與UIWebView相比較,擁有更快的加載速度和性能,更低的內存占用。將UIWebViewDelegate和UIWebView重構成了14個類,3個協議,可以讓開發者進行更加細致的配置。

但是他有一個最致命的缺陷,就是WKWebView的請求不能被NSURLProtocol截獲。而我們團隊開發的app中對於H5容器最佳的優化點主要就在於使用NSURLProtocol技術對於H5進行離線包的處理和H5的圖片和Native的圖片公用一套緩存的技術。因為該問題的存在,目前我們團隊還沒有使用WKWebView代替UIWebVIew。

五、聯系方式

新浪微博
github
簡書首頁

 

文章轉自  景銘巴巴的簡書
 
 
 
 
 
 

一、前言

NSURLProtocol是iOS中URL Loading System的一部分。如果開發者自定義的一個NSURLProtocol並且注冊到app中,那么在這個自定義的NSURLProtocol中我們可以攔截UIWebView,基於系統的NSURLConnection或者NSURLSession進行封裝的網絡請求,然后做到自定義的response返回。非常強大。

二、NSURLProtocol的使用流程

2.1、在AppDelegate中注冊自定義的NSURLProtocol。

比如我這邊自定義的NSURLProtocol叫做YXNSURLProtocol。

在系統加載的時候,把自定義的YXNSURLProtocol注冊到URL加載系統中,這樣 所有的URL請求都有機會進入我們自定義的YXNSURLProtocol進行攔截處理。

加載完成后,當產生URL請求的同時,會依次進入NSURLProtocol的以下相關方法進行處理,下面我們依次來講一下每一個方法的作用。

2.2、NSURLProtocol中的幾個方法

2.2.1、是否進入自定義的NSURLProtocol加載器

 

如果返回YES則進入該自定義加載器進行處理,如果返回NO則不進入該自定義選擇器,使用系統默認行為進行處理。

如果這一步驟返回YES。則會進入2.3的方法中。

2.2.2、重新設置NSURLRequest的信息

 

在這個方法中,我們可以重新設置或者修改request的信息。比如請求重定向或者添加頭部信息等等。如果沒有特殊需求,直接返回request就可以了。但是因為這個方法在會在一次請求中被調用多次(暫時我也不知道什么原因為什么需要回調多洗),所以request重定向和添加頭部信息也可以在開始加載中startLoading方法中重新設置。

2.2.3、這個方法主要是用來判斷兩個request是否相同,如果相同的話可以使用緩存數據,通常調用父類的實現即可

 

這個方法基本不常用。

2.2.4、被攔截的請求開始執行的地方

 

這個函數使我們重點使用的函數。

2.2.5、結束加載URL請求

 

 

2.3、NSURLProtocolClient中的幾個方法

上面的NSURLProtocol定義了一系列加載的流程。而在每一個流程中,我們作為使用者該如何使用URL加載系統,則是NSURLProtocolClient中幾個方法該做的事情。

 

三、實現一個地址重定向的Demo

這個Demo實現的功能是在UIWebView中所有跳轉到sina首頁的請求,都重定位到sohu首頁。

3.1、第一步,新建一個UIWebView,加載sina首頁

 

 

3.2、自定義一個NSURLProtocol

 

 

3.3、在AppDelegate中,進行注冊

 

 

3.4、在canInitWithRequest方法中攔截https://sina.cn/

 

 

3.5、在startLoading中進行方法重定向

 

你也可以選擇在+ (NSURLRequest )canonicalRequestForRequest:(NSURLRequest )request替換request。效果都是一樣的。

3.6、因為新建了一個NSURLConnection *connection,所以要實現他的代理方法,如下

 

通過以上幾步,我們就可以實現最簡單的url重定向,WebView加載新浪首頁,卻跳轉到了搜狐首頁。

四、小結

通過自定義的NSURLProtocol,我們拿到用戶請求的request之后,我們可以做很多事情。比如:

1、自定義請求和響應

2、網絡的緩存處理(H5離線包 和 網絡圖片緩存)

3、重定向網絡請求

4、為測試提供數據Mocking功能,在沒有網絡的情況下使用本地數據返回。

5、過濾掉一些非法請求

6、快速進行測試環境的切換

7、攔截圖片加載請求,轉為從本地文件加載

8、可以攔截UIWebView,基於系統的NSURLConnection或者NSURLSession進行封裝的網絡請求。目前WKWebView無法被NSURLProtocol攔截。

9、當有多個自定義NSURLProtocol注冊到系統中的話,會按照他們注冊的反向順序依次調用URL加載流程。當其中有一個NSURLProtocol攔截到請求的話,后續的NSURLProtocol就無法攔截到該請求。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM