WKWebViewのスクリプトの実行を同期処理にする方法

2022/10/28
2020/01/09

はじめに

iOS13からUIWebViewがDeprecated(廃止予定)になり、本格的にWKWebViewへの移行を検討する必要が出てきました。

Updating Apps that Use Web Views

The App Store will no longer accept new apps using UIWebView as of April 2020 and app updates using UIWebView as of December 2020.

Updating Apps that Use Web Views より

新規アプリは2020年4月以降にUIWebViewをアプリで使用しているとリジェクトされ、アップデートの更新は2020年12月以降にUIWebViewをアプリで使用しているとリジェクトされてしまうようです。

本記事では、UIWebViewからWKWebViewへ移行する際にスクリプト実行の処理を同期処理として実装する方法を紹介します。

実行環境

  • macOS Mojave 10.14.6
  • Xcode10.2

前提

スクリプト実行処理の実装にあたって、今回はUser Agentの取得処理を用いて説明します。

UIWebViewでのスクリプト実行

UIWebViewでスクリプトを実行する場合は stringByEvaluatingJavaScriptFromString を使用します。

// UIWebViewの初期化
UIWebView *webView = [[UIWebView alloc] init];
 
// JavaScriptの実行
NSString *userAgent = [webView stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"];

stringByEvaluatingJavaScriptFromString の場合は同期処理なので、スクリプトの実行完了まで後続の処理は待機します。

WKWebViewでのスクリプト実行

WKWebViewでスクリプトを実行する場合は evaluateJavaScript を使用します。

// WKWebViewの初期化
WKWebView *webView = [[WKWebView alloc] init];
__block NSString *userAgent = @"";
 
// JavaScriptの実行
[webView evaluateJavaScript:@"document.title"
          completionHandler:^(NSString *result, NSError *error) {
    userAgent = result;
}];
 
NSLog(userAgent); // userAgentは取得できてない

evaluateJavaScript の場合は実行完了後の処理をコールバックで受け取るため、後続の処理をそのまま実行してしまいます。

userAgentに結果を代入する前にNSLogを実行するため、結果は空となってしまいます。

WKWebViewで同期的にスクリプト実行する方法

非同期の処理を同期処理にするために NSRunLoop を使用します。

// WKWebViewの初期化
WKWebView *webView = [[WKWebView alloc] init];
__block NSString *userAgent = @"";
__block BOOL keepAlive = YES;
 
[webView evaluateJavaScript:@"navigator.userAgent"
          completionHandler:^(NSString *result) {
    userAgent = result;
    keepAlive = NO;
}];
 
// フラグが変更されるまで待機
while (keepAlive) {
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
                             beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
}
 
NSLog(userAgent); // userAgentは取得できる!

フラグが変更されるまで0.1秒毎にループして、コールバックの中でフラグを更新することでループを抜け出します。

同期処理にする方法として、DispatchSemaphore も候補に挙げられますが、今回の場合はデッドロックが発生するためうまくいきません。

evaluateJavaScriptはメインスレッドで処理する必要があるため、dispatch_semaphore_waitのタイミングでJavaScriptの処理も止まってしまいます。

まとめ

今回は、UIWebViewからWKWebViewへ移行する際にスクリプト実行の処理を同期処理として記述する方法を紹介しました。

iOS の記事