唐弢 發表於 2024-8-3 18:28:00

iOS开发基础146-深入解析WKWebView

<p><code>WKWebView</code>是苹果在iOS 8中引入的重要组件,它替代了<code>UIWebView</code>,为开发者提供了高性能、高稳定性的网页显示和交互能力。在本文中,我们将深入探讨<code>WKWebView</code>的底层架构、关键特性、使用方法和高级功能。</p>
<h2 id="一wkwebview的底层架构">一、WKWebView的底层架构</h2>
<p><code>WKWebView</code>基于WebKit框架,采用多进程架构,将页面渲染和JavaScript执行放在独立的Web进程中,这样做的好处是主应用进程与Web内容进程隔离,能显著提升应用的稳定性和安全性。其架构主要包括以下几个部分:</p>
<h3 id="1-web内容进程">1. Web内容进程</h3>
<p>负责HTML解析、CSS解析、JavaScript执行、页面渲染等操作。这些操作都是在独立的进程中进行,防止网页崩溃影响整个应用。</p>
<h3 id="2-网络进程">2. 网络进程</h3>
<p>负责网络请求的管理和缓存数据的处理,从数据源获取网页内容,并传输给Web内容进程。</p>
<h3 id="3-ui进程">3. UI进程</h3>
<p>主要负责与用户的交互,如接收用户输入、发送消息给Web内容进程等。UI进程与Web内容进程通过IPC(进程间通信)进行信息的传递。</p>
<p>如下图所示是<code>WKWebView</code>的架构示意图:</p>
<pre><code>+------------------+                +------------------+
|                  | &lt;------------&gt; |                  |
|      UI进程       |                |    Web内容进程   |
|                  |    IPC 通信      |                  |
+------------------+                +------------------+
         ^                                 ^
         |                                 |
         v                                 v
+------------------+                +------------------+
|                  |                |                  |
|   WKWebView    |                |   页面引擎      |
|                  |                |                  |
+------------------+                +------------------+
</code></pre>
<h2 id="二wkwebview的基本使用">二、WKWebView的基本使用</h2>
<h3 id="1-初始化wkwebview">1. 初始化WKWebView</h3>
<p>要使用<code>WKWebView</code>,首先需要进行基本初始化和配置工作。不同于<code>UIWebView</code>,初始化<code>WKWebView</code>时需指定其配置属性。</p>
<pre><code class="language-objc">#import &lt;WebKit/WebKit.h&gt;

@interface ViewController ()
@property (nonatomic, strong) WKWebView *webView;
@end

@implementation ViewController

- (void)viewDidLoad {
    ;
   
    // 创建配置对象
    WKWebViewConfiguration *configuration = [ init];
   
    // 初始化WKWebView
    self.webView = [ initWithFrame:self.view.bounds configuration:configuration];
   
    // 设置内边距
    ;
   
    // 加载一个网页示例
    NSURL *url = ;
    NSURLRequest *request = ;
    ;
}

@end
</code></pre>
<h3 id="2-加载本地文件">2. 加载本地文件</h3>
<p>除了加载网络资源外,<code>WKWebView</code>还可以加载本地文件:</p>
<pre><code class="language-objc">NSString *htmlPath = [ pathForResource:@"index" ofType:@"html"];
NSURL *baseURL = bundlePath]];
NSString *htmlContent = ;
;
</code></pre>
<h3 id="3-导航控制">3. 导航控制</h3>
<p><code>WKWebView</code>提供了丰富的导航控制方法,帮助我们处理网页的前进、后退和刷新等操作:</p>
<pre><code class="language-objc">// 刷新当前页面
;

// 停止加载
;

// 后退到上一页面
;

// 前进到下一页面
;
</code></pre>
<h3 id="4-获取网页内容">4. 获取网页内容</h3>
<p><code>WKWebView</code>的一个强大功能是可以直接执行JavaScript代码并获取返回值:</p>
<pre><code class="language-objc">[self.webView evaluateJavaScript:@"document.title" completionHandler:^(id result, NSError *error) {
    if (!error) {
      NSLog(@"Page title: %@", result);
    }
}];
</code></pre>
<h2 id="三wkwebview的代理与回调">三、WKWebView的代理与回调</h2>
<p><code>WKWebView</code>提供了两个主要的代理协议:<code>WKNavigationDelegate</code>和<code>WKUIDelegate</code>,它们分别处理导航和用户界面方面的回调。</p>
<h3 id="1-wknavigationdelegate">1. WKNavigationDelegate</h3>
<p>该协议管理网页内容的加载过程,包括开始、完成、失败等事件:</p>
<pre><code class="language-objc">@interface ViewController () &lt;WKNavigationDelegate&gt;
@end

@implementation ViewController

- (void)viewDidLoad {
    ;

    // 设置导航代理
    self.webView.navigationDelegate = self;
   
    // 加载网页
    NSURL *url = ;
    NSURLRequest *request = ;
    ;
}

// 页面开始加载
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
    NSLog(@"页面开始加载");
}

// 内容开始返回
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation {
    NSLog(@"内容开始返回");
}

// 页面加载完成
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
    NSLog(@"页面加载完成");
}

// 页面加载失败
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error {
    NSLog(@"页面加载失败,错误: %@", error.localizedDescription);
}
@end
</code></pre>
<h3 id="2-wkuidelegate">2. WKUIDelegate</h3>
<p>该协议处理网页中的UI事件,比如显示JavaScript的<code>alert</code>、<code>confirm</code>、<code>prompt</code>对话框:</p>
<pre><code class="language-objc">@interface ViewController () &lt;WKUIDelegate&gt;
@end

@implementation ViewController

- (void)viewDidLoad {
    ;

    // 设置用户界面代理
    self.webView.UIDelegate = self;
   
    // 加载网页
    NSURL *url = ;
    NSURLRequest *request = ;
    ;
}

// JavaScript alert框
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
    UIAlertController *alert = ;
    UIAlertAction *ok = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
      completionHandler();
    }];
    ;
    ;
}

// JavaScript confirm框
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler {
    UIAlertController *alert = ;
    UIAlertAction *ok = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
      completionHandler(YES);
    }];
    UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
      completionHandler(NO);
    }];
    ;
    ;
    ;
}

// JavaScript prompt框
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler {
    UIAlertController *alert = ;
    [alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
      textField.text = defaultText;
    }];
    UIAlertAction *ok = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
      NSString *input = alert.textFields.firstObject.text;
      completionHandler(input);
    }];
    UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
      completionHandler(nil);
    }];
    ;
    ;
    ;
}

@end
</code></pre>
<h2 id="四wkwebview的进阶使用">四、WKWebView的进阶使用</h2>
<h3 id="1-与javascript交互">1. 与JavaScript交互</h3>
<p>通过<code>WKScriptMessageHandler</code>协议,<code>WKWebView</code>可以和网页中的JavaScript进行双向交互。</p>
<h4 id="前提配置">前提配置</h4>
<p>需要在<code>WKWebViewConfiguration</code>中配置内容控制器<code>WKUserContentController</code>并注册JavaScript消息处理器:</p>
<pre><code class="language-objc">@interface ViewController () &lt;WKScriptMessageHandler&gt;
@end

@implementation ViewController

- (void)viewDidLoad {
    ;

    WKWebViewConfiguration *config = [ init];
    WKUserContentController *contentController = [ init];
    ;
    config.userContentController = contentController;

    self.webView = [ initWithFrame:self.view.bounds configuration:config];
    ;

    NSString *html = @"&lt;html&gt;&lt;body&gt;&lt;button onclick=\"window.webkit.messageHandlers.nativeHandler.postMessage('Hello from JS!');\"&gt;Click Me&lt;/button&gt;&lt;/body&gt;&lt;/html&gt;";
    ;
}

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    if () {
      NSLog(@"Received message from JS: %@", message.body);
    }
}

- (void)dealloc {
    ;
}

@end
</code></pre>
<p>这样,当点击网页按钮时,JavaScript会将消息发送到原生代码并触发<code>userContentController:didReceiveScriptMessage:</code>回调。</p>
<h3 id="2-loading进度条">2. Loading进度条</h3>
<p>通过监听<code>WKWebView</code>的<code>estimatedProgress</code>属性,我们可以实现网页加载过程中的进度条显示:</p>
<pre><code class="language-objc">@interface ViewController ()

@property (nonatomic, strong) UIProgressView *progressView;

@end

@implementation ViewController

- (void)viewDidLoad {
    ;

    // 初始化WKWebView
    self.webView = [ initWithFrame:self.view.bounds];
    ;

    // 初始化进度条
    self.progressView = [ initWithProgressViewStyle:UIProgressViewStyleDefault];
    self.progressView.frame = CGRectMake(0, 88, self.view.bounds.size.width, 2);
    ;

    // 观察estimatedProgress属性
    ;

    // 加载网页
    NSURL *url = ;
    NSURLRequest *request = ;
    ;
}

- (void)dealloc {
    ;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary&lt;NSKeyValueChangeKey,id&gt; *)change context:(void *)context {
    if () {
      self.progressView.progress = self.webView.estimatedProgress;
      if (self.webView.estimatedProgress &gt;= 1.0) {
            [UIView animateWithDuration:0.5 animations:^{
                self.progressView.alpha = 0.0;
            }];
      } else {
            self.progressView.alpha = 1.0;
      }
    } else {
      ;
    }
}

@end
</code></pre>
<h3 id="3-处理文件上传">3. 处理文件上传</h3>
<p><code>WKWebView</code>支持文件上传,通过实现<code>UIDocumentPickerViewController</code>,我们可以定制上传文件的操作:</p>
<pre><code class="language-objc">@interface ViewController () &lt;WKUIDelegate, UIDocumentPickerDelegate&gt;
@end

@implementation ViewController

- (void)viewDidLoad {
    ;

    // 初始化WKWebView
    WKWebViewConfiguration *configuration = [ init];
    self.webView = [ initWithFrame:self.view.bounds configuration:configuration];
    self.webView.UIDelegate = self;
    ;

    // 加载网页
    NSURL *url = ;
    NSURLRequest *request = ;
    ;
}

- (void)webView:(WKWebView *)webView runOpenPanelWithParameters:(WKOpenPanelParameters *)parameters initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSArray&lt;NSURL *&gt; * _Nullable URLs))completionHandler {
    UIDocumentPickerViewController *documentPicker = [ initWithDocumentTypes:@[@"public.item"] inMode:UIDocumentPickerModeOpen];
    documentPicker.delegate = self;
    documentPicker.completionHandler = ^(NSArray&lt;NSURL *&gt; * _Nonnull urls) {
      completionHandler(urls);
    };
    ;
}

@end
</code></pre>
<h2 id="五wkwebview的性能优化">五、WKWebView的性能优化</h2>
<p>由于<code>WKWebView</code>在实际使用中可能会面临性能问题,以下是一些性能优化的建议:</p>
<h3 id="1-缓存策略">1. 缓存策略</h3>
<p>通过使用合适的缓存策略,你可以避免重复加载相同的资源,从而提高加载速度。如使用<code>URLCache</code>配置:</p>
<pre><code class="language-objc">NSURLCache *urlCache = [ initWithMemoryCapacity:1024 * 1024 * 10
                                                    diskCapacity:1024 * 1024 * 50
                                                      diskPath:@"wkwebview_cache"];
;

NSURLRequest *request = ;
;
</code></pre>
<h3 id="2-异步加载资源">2. 异步加载资源</h3>
<p>避免同步加载资源导致主线程阻塞,可以使用异步加载的方法来处理:</p>
<pre><code class="language-objc">dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    NSURL *url = ;
    NSData *data = ;
    dispatch_async(dispatch_get_main_queue(), ^{
      ];
    });
});
</code></pre>
<h3 id="3-减少dom操作">3. 减少DOM操作</h3>
<p>在需要频繁操作DOM时,尽量将多个操作合并为一次,以减少引擎的渲染负担:</p>
<pre><code class="language-javascript">function updateContent() {
    let container = document.getElementById('container');
    let fragment = document.createDocumentFragment();

    for (let i = 0; i &lt; 1000; i++) {
      let div = document.createElement('div');
      div.textContent = `Item ${i}`;
      fragment.appendChild(div);
    }

    container.appendChild(fragment);
}
</code></pre>
<h2 id="六oc与javascript通信进阶">六、OC与JavaScript通信进阶</h2>
<p>如果只是传递简单的用户信息数据,除了通过 <code>WKScriptMessageHandler</code> 的方式,还有以下几种方法可以将数据从客户端(Objective-C/Swift)传递给 JavaScript。</p>
<h3 id="1通过-url-scheme">1、通过 URL Scheme</h3>
<p>这种方法主要是在加载网页的时候,将用户信息作为查询参数(query parameter)嵌入到 URL 中传递给页面。这种方式适用于初始加载页面的数据传递。</p>
<pre><code class="language-objc">// 构建用户信息数据
NSString *userInfo = @"userId=12345&amp;userName=JohnDoe";
NSString *urlString = ;
NSURL *url = ;
NSURLRequest *request = ;
;
</code></pre>
<p>在 JavaScript 中可以通过 <code>window.location.search</code> 获取查询参数。</p>
<h3 id="2通过-evaluatejavascript-执行-javascript">2、通过 evaluateJavaScript 执行 JavaScript</h3>
<p><code>evaluateJavaScript:completionHandler:</code> 是一个简单直接的方法,可以在客户端执行任意 JavaScript 代码并通过回调获取执行结果。</p>
<pre><code class="language-objc">// 构建JavaScript代码
NSString *userId = @"12345";
NSString *userName = @"JohnDoe";
NSString *jsCode = ;

// 执行JavaScript代码
[self.webView evaluateJavaScript:jsCode completionHandler:^(id result, NSError *error) {
    if (error) {
      NSLog(@"Error: %@", error.localizedDescription);
    }
}];
</code></pre>
<p>在网页中,需要定义对应的 JavaScript 函数来接收这些数据:</p>
<pre><code class="language-html">&lt;script&gt;
function setUserInfo(userId, userName) {
    console.log("User ID: " + userId);
    console.log("User Name: " + userName);
    // 其他业务逻辑
}
&lt;/script&gt;
</code></pre>
<h3 id="3通过-user-scripts">3、通过 User Scripts</h3>
<p>如果你想在页面加载的初始阶段注入数据,可以使用 <code>WKUserScript</code> 来添加 JavaScript 预处理。</p>
<pre><code class="language-objc">// 构建JavaScript代码
NSString *userId = @"12345";
NSString *userName = @"JohnDoe";
NSString *scriptSource = ;

// 创建用户脚本
WKUserScript *userScript = [ initWithSource:scriptSource injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];

// 添加用户脚本到配置
WKWebViewConfiguration *config = [ init];
;

// 创建并加载 WKWebView
self.webView = [ initWithFrame:self.view.bounds configuration:config];
NSURL *url = ;
NSURLRequest *request = ;
;
</code></pre>
<p>通过上述方法,页面在加载时就会自动注入用户信息,网页可以在任何地方直接访问 <code>window.userInfo</code>。</p>
<h3 id="4通过-documentcookie不推荐">4、通过 Document.cookie(不推荐)</h3>
<p>虽然不太推荐,但我们也可以通过设置 <code>Document.cookie</code> 将信息传递给网页。以下是示例:</p>
<pre><code class="language-objc">NSString *userId = @"12345";
NSString *userName = @"JohnDoe";
NSString *cookieScript = ;
[self.webView evaluateJavaScript:cookieScript completionHandler:^(id result, NSError *error) {
    if (error) {
      NSLog(@"Error: %@", error.localizedDescription);
    }
}];
</code></pre>
<p>在网页中,可以通过 JavaScript 解析 <code>document.cookie</code> 获取用户信息。</p>
<pre><code class="language-javascript">function getCookie(name) {
    let value = `; ${document.cookie}`;
    let parts = value.split(`; ${name}=`);
    if (parts.length === 2) return parts.pop().split(';').shift();
}

let userId = getCookie('userId');
let userName = getCookie('userName');
console.log("User ID: " + userId);
console.log("User Name: " + userName);
</code></pre>
<h3 id="选择">选择</h3>
<p>以上方法各有优劣,根据实际使用场景选择适合的方法:</p>
<ul>
<li><strong>如果是初始加载时传递数据,用 URL Scheme 比较简单直接。</strong></li>
<li><strong>如果需要在页面加载后随时传递数据,<code>evaluateJavaScript:completionHandler:</code> 非常灵活。</strong></li>
<li><strong>需要在页面加载前就注入数据,<code>WKUserScript</code> 是一种好方法。</strong></li>
<li><strong><code>Document.cookie</code> 方式虽然可以传递数据,但不推荐用于敏感信息。</strong></li>
</ul>
<h2 id="七总结">七、总结</h2>
<p><code>WKWebView</code>提供了现代化的网页视图解决方案,具有高性能、高稳定性的优势。通过理解其底层架构、掌握常用和进阶的使用方法、如何与JavaScript进行交互和处理实际应用中的各种需求,你可以更好地实现复杂的网页加载与交互功能,提升应用的用户体验和性能。</p>


</div>
<div id="MySignature" role="contentinfo">
    将来的你会感谢今天如此努力的你!
版权声明:本文为博主原创文章,未经博主允许不得转载。<br><br>
来源:https://www.cnblogs.com/chglog/p/18340886
頁: [1]
查看完整版本: iOS开发基础146-深入解析WKWebView