主にプログラミング関連のメモ帳 ♪(✿╹ヮ╹)ノ
書いてあるコードは自己責任でご自由にどうぞ。記事本文の無断転載は禁止です。
2023/05/10
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 ビルドに上記コードを含めるとおそらくリジェクトされると思うので、開発時だけにしましょう。
参考: