一. 背景
线上捕获到了关于_SFAppPasswordSavingViewController
的相关溃散。具体溃散仓库如下:
这个溃散呈现在钱包提现输入暗码页面。
二. 原因剖析
从溃散原因剖析:
Application Specific Information:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Application tried to present modally a view controller <_SFAppPasswordSavingViewController: 0x120d60030> that is already being presented by <UIKeyboardHiddenViewController_Save: 0x123849e30>.
UserInfo:(null)'
是因为Application
测验去present
存储暗码_SFAppPasswordSavingViewController
页面,但这个_SFAppPasswordSavingViewController
已经present
过,导致呈现这个溃散。而从发生该溃散体系剖析来看,这个溃散根本发生在iOS16
及相关体系,从现在来看应该是体系原因,首要呈现键盘弹起主动暗码填充界面。
我们从苹果论坛上面也能看到关于这个crash
的相关反馈:
developer.apple.com/forums/thre…
里面有人说到针对这个溃散的解决办法如下:
也就是hook
了UIViewController
的presentViewController:animated:completion:
办法,然后在hook
办法里面增加判别当时的VC
的presentedViewController
是否有值,如果为nil
,表示还没present
过,直接调用“presentViewController:animated:completion:,如果有值,表示当时
VC已经
present过了,直接
return`。
#import "UIViewController+PresentKBHook.h"
@implementation UIViewController (PresentKBHook)
+ (void)load {
[UIViewController ff_swizzleInstanceMethodWithSrcClass:[UIViewController class] srcSel:@selector(presentViewController:animated:completion:) swizzledSel:@selector(cr_presentViewController:animated:completion:)];
}
- (void)cr_presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion {
if (self.presentedViewController) {
NSLog(@"[present devil]self=%@,toPresent=%@",self.description, viewControllerToPresent.description);
}else{
[self cr_presentViewController:viewControllerToPresent animated:flag completion:completion];
}
}
/**
Implementation of exchanging two object methods
*/
+ (void)ff_swizzleInstanceMethodWithSrcClass:(Class)srcClass
srcSel:(SEL)srcSel
swizzledSel:(SEL)swizzledSel{
Method srcMethod = class_getInstanceMethod(srcClass, srcSel);
Method swizzledMethod = class_getInstanceMethod(srcClass, swizzledSel);
if (!srcClass || !srcMethod || !swizzledMethod) return;
//Add a layer of protection measures. If the method is added successfully, it means that the method does not exist in this class, but exists in the parent class, and the method of the parent class cannot be exchanged, otherwise the method will crash when the parent class object calls the method; Adding failure indicates that the method exists in this class
BOOL addMethod = class_addMethod(srcClass, srcSel, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (addMethod){
//After the IMP is successfully implemented by adding methods, the original implementation will be replaced with the swizzledMethod method to achieve method exchange without affecting the implementation of the parent method
class_replaceMethod(srcClass, swizzledSel, method_getImplementation(srcMethod), method_getTypeEncoding(srcMethod));
}else{
//Adding failed, calling the implementation of the two interactive methods
method_exchangeImplementations(srcMethod, swizzledMethod);
}
}
@end
三. 解决方案
- 增加降级开关、增加体系版本判别、只在
iOS 16
相关体系启用该溃散管理。
/// 键盘溃散修正战略
public struct FJFPresentCrashFixPolicy: HandyJSON {
/// presnet事情 溃散修正
public var viewControllerPresentCrashFixEnable: Bool = false
/// 开端 修正 版本
public var startFixVersion: String = "16.0"
/// 结束 修正 版本
public var endFixVersion: String = "17.0"
public init() {}
}
/// 是否为需要修正的体系版本
public static func isNeedToFixSystemVersionCrash(startVersion: String, endVersion: String) -> Bool {
let curVersion = UIDevice.current.systemVersion
if curVersion.compare(startVersion, options: .numeric) == .orderedDescending,
curVersion.compare(endVersion, options: .numeric) == .orderedAscending {
return true
}
return false
}
}