アプリのクラッシュは開発者にとって頭の痛い問題であり、バグを迅速に修正するために例外を捕捉して特定することは、開発者のスキルを試されるものです。 iOS システムの場合、ランタイムと拡張機能を使用して例外を処理し、クラッシュを防ぐためのフレンドリーなプロンプトを提供することは良い選択ですが、より面倒です。幸いなことに、システムはすべての例外をキャッチできる NSSetUncaughtExceptionHandler API を提供します。今回は主にSwiftでのこの機能の使い方を紹介します。まずレンダリングを見てみましょう。
1. 例外のキャプチャと処理に関連するメソッドを定義します。
このコードはこのブログを参照しています https://github.com/lbwxly/CrashHandler 。NSSetUncaughtExceptionHandler が使用されていないため、テスト効果はあまり満足のいくものではありません
import Foundation
public typealias Completion = ()->Void;
public typealias CrashCallback = (String,Completion)->Void;
public var crashCallBack: CrashCallback?
func signalHandler(signal:Int32) -> Void {
let stackTrace = Thread.callStackSymbols.joined(separator: "\r\n")
crashCallBack?(stackTrace,{
unregisterSignalHandler();
exit(signal);
});
}
func registerSignalHanlder()
{
objc_setUncaughtExceptionHandler { (_response:Any?) in
if let signal = _response as? Int32 {
signalHandler(signal: signal)
}
else if let exception = _response as? NSException {
// print("++++++++++++++++++ 异常信息 ++++++++++++++++++")
// NSLog("name:%@",exception.name.rawValue)
// NSLog("reason:%@",exception.reason ?? "--")
// NSLog("userInfo:%@",exception.userInfo ?? [:])
// NSLog("Stack Trace:%@",exception.callStackSymbols)
// NSLog("Stack ReturnAddresses:\n%@", exception.callStackReturnAddresses)
// NSLog("Exception All:\n%@", exception)
crashCallBack?(exception.reason ?? exception.callStackSymbols.joined(separator: "\r\n"),{
unregisterSignalHandler();
exception.raise()
});
}
}
signal(SIGINT, signalHandler);
signal(SIGSEGV, signalHandler);//野指针,僵尸对象
signal(SIGTRAP, signalHandler);
signal(SIGABRT, signalHandler);
signal(SIGILL, signalHandler);
signal(SIGBUS, signalHandler);
signal(SIGFPE, signalHandler);
signal(SIGTERM, signalHandler);
signal(SIGKILL, signalHandler);//CPU无法执行的代码(比如无效指令、除以0等)
signal(SIGPIPE, signalHandler);
}
func unregisterSignalHandler()
{
signal(SIGINT, SIG_DFL);
signal(SIGSEGV, SIG_DFL);
signal(SIGTRAP, SIG_DFL);
signal(SIGABRT, SIG_DFL);
signal(SIGILL, SIG_DFL);
signal(SIGBUS, SIG_DFL);
signal(SIGFPE, SIG_DFL);
signal(SIGTERM, SIG_DFL);
signal(SIGKILL, SIG_DFL);
signal(SIGPIPE, SIG_DFL);
}
public class CrashHandler
{
public static func setup(callBack:@escaping CrashCallback){
crashCallBack = callBack;
registerSignalHanlder();
}
}
2. 電話をかける
DidFinishLaunchingWithOptions はグローバル メソッドであるため、すべての例外をキャッチするにはここで呼び出しを登録します。
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
//输出异常日志
CrashHandler.setup {[weak self] (stackTrace, completion) in
self?.showExceptionFor(Reason: stackTrace)
completion()
}
return true
}
}
3. 例外をキャッチした後の処理
ホームページに戻ったり、ログをサーバーにアップロードしたり、特定の操作を自由に実行できます。
//MARK: - 异常处理
extension AppDelegate {
private func showExceptionFor(Reason reason:String,
andTitle t:String = "app异常"){
//var dismiss = false
let currentRunloop = CFRunLoopGetCurrent()
if let vc = UIApplication.shared.keyWindow?.rootViewController {
Utils.shareInstance().showBoxFor(ViewController: vc,
andMessage: t,
andOK: "提交日志,并返回首页",
andOKBlock: {[weak self] action in
print("异步提交异常日志")
//...
//返回首页
var arr = self?.navController.popToRootViewController(animated: false)
Utils.shareInstance().gotoRoot(ViewController: arr)
arr?.removeAll()
//dismiss = true
},
andCancle: nil,
withInfo: reason)
}
if let arrModes = CFRunLoopCopyAllModes(currentRunloop) as? Array<CFString> {
//while !dismiss {
while true {
for model in arrModes {
CFRunLoopRunInMode(CFRunLoopMode.init(rawValue: model), 0.001, false)
}
}
}
}
}
PopToRootViewController メソッドの呼び出しによってメモリ リークが発生するため、現時点ではこれに対処する良い方法はありません。