iOS で WebView を使っている場合、何らかの理由で WebView のプロセスが死んだ場合は webViewWebContentProcessDidTerminate
が呼ばれるんですが、原因が分からず、なんとも困った感じになります。
ということで、頑張ってなんでクラッシュしたのかの大まかな理由を取得します。
理由自体は、上記メソッド名に withReason
を付け足したような webContentProcessDidTerminateWithReason
メソッドを定義することで受け取れます。
ただし、非公開 API であるため、通常の手順で使用することは出来ません。
ということで、まずは以下のようにヘッダーを実装します。
#import <Foundation/Foundation.h> #import <WebKit/WebKit.h> // 該当イベントを受け取るためのインターフェース @protocol WKNavigationDelegateExtended <WKNavigationDelegate> @optional - (void)webView:(WKWebView*)webView webContentProcessDidTerminateWithReason:(NSInteger)reason; @end // WebView のラッパー @interface WebViewWrapper : NSObject<WKNavigationDelegate> - (void)alloc:(WKWebView*)webView; - (void)setNavigationDelegateExtended:(id<WKNavigationDelegate>)navigationDelegate; @end
本体はこんな感じです。
#import "WebViewWrapper.h" @implementation WebViewWrapper { __weak WKWebView* _webView; __weak id<WKNavigationDelegate> _navigationDelegate; } - (void)alloc:(WKWebView*) webView { _webView = webView; _webView.navigationDelegate = self; } - (void)setNavigationDelegateExtended:(id<WKNavigationDelegate>) navigationDelegate { _navigationDelegate = navigationDelegate; } - (void)_webView:(WKWebView*) webView webContentProcessDidTerminateWithReason:(NSInteger) reason { if (webView != _webView) { return; } __strong id<WKNavigationDelegateExtended> delegate = _navigationDelegate; if (delegate && [delegate respondsToSelector:@selector(webView:webContentProcessDidTerminateWithReason:)]) { [delegate webView:webView webContentProcessDidTerminateWithReason:reason]; } } @end
さすがに ObjC で全部書くのはつらいので、こっからは Swift で使います。
Swift 側はこんな感じ。
import Foundation import WebKit class WebView : NSObject { private var _webView: WKWebView! private var _wrapper: WebViewWrapper! init() { self._webView = WKWebView(...) self._wrapper = WebViewWrapper() self._wrapper.alloc(webView: self._webView) self._wrapper.setNavigationDelegateExtended(self) } } extension WebView: WKNavigationDelegateExtended { // https://github.com/WebKit/WebKit/blob/main/Source/WebKit/UIProcess/Cocoa/NavigationState.mm#L1025-L1048 func webView(_ webView: WKWebView, webContentProcessDidTerminateWithReason reason: NSInteger) { switch reason { case 0: // Memory Limit break case 1: // CPU Limit break case 2: // Request by Application break case 3: // Process Count Limit // Unresponsive // Request by Network // Request by GPU Process // Crash break default: // Unreachable break } } }
これで、大まかではありますが原因がアプリ側から取得できます。
パラメータの reason
には、本来 _WKProcessTerminationReason
型が渡されるのですが、実態は Enum で定義されている型なので、 NSInteger
で取得しても問題ありません。
中身は、上記コードのコメントの WebKit/WebKit のソースを見ると良いでしょう。
わたしはアプリがクラッシュするから調べてーって言われて Safari の DevTools 繋いで再現した!って喜んで詳しく調べたら、結局単純に Safari がクラッシュしてるだけでした。
ということで、メモでした。
ちなみに Release ビルドに上記コードを含めるとおそらくリジェクトされると思うので、開発時だけにしましょう。
参考: