背景:
最近在整理bugly上的问题,顺便整理记录下来,方便后面开发的时候避免犯一样的错误.ios现在的系统版本都到13点几了,而我们app之前支持到ios8,后面升级到ios10.所以我们解决的问题主要还是针对ios10及以上.在解决问题的同时发现ios11以下也就是ios10点多有很大的差别.或者说ios11开始系统做了好多容错处理,好多本来应该闪退的在11及以上版本都不闪退了.如后面会提到的OKV、通知等监听,键盘和数组safe的冲突导致的闪退.
关于NSExceptionName:
常见的就4种,其他的至少在我们项目没见过.下面就先根据异常名字简单翻译理解下.
NSGenericException:一般异常
NSMallocException:内存不足异常
NSObjectInaccessibleException:对象不可访问异常
NSObjectNotAvailableException:对象不可用异常
NSDestinationInvalidException:目标无效异常
NSPortTimeoutException:端口超时异常
NSInvalidSendPortException:无效发送端口异常
NSInvalidReceivePortException:无效接收端口异常
NSPortSendException:端口发送异常
NSPortReceiveException:端口接收异常
NSOldStyleException:老样式异常
重点分享4类异常:
NSRangeException:范围异常/越界异常
问题:
使用的数组下标超出数组最大下标值
比如数组长度count, index的下标范围[0, count -1], 在开发时,可能index的最大值超过数组的范围
这可能是不同模块的处理没有对应上
使用的数组下标是一个非正常值
如果小标(index)的值是由其他模块的变量传递进来的,这就会有很大的不确定性, 可能是一个很大的整数值
很明显,上面的值分别是32位和64位下的最大整数值,可能的情况就是传递的 index 参数获取了无效的值,但仍然拿到 NSMutableArray 中使用
比如 index 通过下面的方式获取
如果找不到 str ,则返回 NSNotFound,32位下它就是2147483647,64位下18446744073709551615 ,将它作为参数传递则会导致 NSRangeException。
Cannot remove an observer for the key path"currentViewController.viewModel.selectedGoodsCount" from because it is not registered as an observer.
[self addObserver:self forKeyPath:@"currentVC.viewModel.selectedGoodsCount" options:NSKeyValueObservingOptionNew context:nil];
[self removeObserver:self forKeyPath:@"currentVC.viewModel.selectedGoodsCount"];
空数组的操作
如果一个数组刚刚初始化,还是空的,就对它进行相关读取操作
处理的数据范围 NSRange 超过数据本身的长度
-[__NSArrayM removeObjectsInRange:]: range {1, 1} extends beyond bounds [0 .. 0]
Cannot remove an observer for the key path"currentViewController.viewModel.selectedGoodsCount" from because it is not registered as an observer.
[self addObserver:self forKeyPath:@"currentVC.viewModel.selectedGoodsCount" options:NSKeyValueObservingOptionNew context:nil];
[self removeObserver:self forKeyPath:@"currentVC.viewModel.selectedGoodsCount"];
KVO监听移除了2次,或还没添加就运行了移除,也会报数据越界.一般移除代码会写在dealloc方法,添加代码比较习惯写到viewDidLoad方法.这样正常情况下是不会有问题的,但是极端情况下,手机卡了等情况如果导致你的VC创建了,当没有能加载完成显示出来就直接回收了,就会导致没有添加监听直接走移除监听而导致上面出现的闪退.所以这里给的建议是可以把监听的添加代码放在init方法处理,让生命周期对称使用.或用runtime原理来做统一的交换safe保护下哪怕移除了多次监听也不让闪退.
NSInvalidArgumentException:无效参数异常
1.运行时插入的Object为nil
2.或者调用一个没有实现的方法
3.performSegue但是没有在storyboard里面连线
4.返回的是id类型强转为另外一个类型,并调用方法出现找不到
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason:'-[__NSDictionaryI setObject:forKey:]: unrecognized selector sent to instance 0x60000389e500'
NSString *result=@"{\"username\":\"aaa\",\"phone\":\"18273948475\",\"bankcount\":\"k3232323\"}";
NSData *data = [result dataUsingEncoding:NSUTF8StringEncoding];
NSMutableDictionary *info = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
if (info) {
NSString *username = info[@"username"];//[Utils UrlDecode: info[@"username"]];
[info setObject:username forKey:@"username"];
}
从JSON数据创建一个Foundation对象。如果解析器应该允许不是NSArray或NSDictionary的顶级对象,请设置NSJSONReadingAllowFragments选项。设置NSJSONReadingMutableContainers选项将使解析器生成可变NSArray和NSDictionary。设置NSJSONReadingMutableLeaves选项将使解析器生成可变的NSString对象。如果在解析过程中发生错误,那么将设置错误参数,结果将为零。
UITabBarController *tabBarController = (UITabBarController *)[UIApplication sharedApplication].keyWindow.rootViewController;
UINavigationController *navigationController = tabBarController.selectedViewController;
像这种强转就要特别注意,如果当前的rootViewController不是UITabBarController类型就会出现闪退.
正常写代码肯定不会这样写,但是“loc_str”这个参数一般是上个页面给或服务接口给的,这样就不可控了,只要出现nil的情况就会闪退.
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason:'NSConcreteMutableAttributedString initWithString:: nil value'
NSString *loc_str=nil;
NSMutableAttributedString *attriutedString = [[NSMutableAttributedString alloc] initWithString:loc_str];
在对象调用了自己没有的属性或方法是发生的闪退:如UIView 调用了 UIViewController 的方法时发生的闪退。
一般情况下不会发生类似问题,主要是在页面传值或者使用执行集中跳转逻辑的时候由于传参不合适或者是代码不规范造成问题。
*** -[UIWindow navigationController]: unrecognized selector sent to instance 0x101b07d60
if (! [viewController isKindOfClass:[UIViewController class]]) return;
[viewController.navigationController pushViewController:[self needJumpToVCName:vcClassName withParameter:parameter] animated:YES];
在构造 NSMutableAttributedString 或者 NSAttributedString 设置的属性值为 nil 导致闪退。
这个问题比较容易忽略,在实例化该对象是一定要小心、规范。
*** NSConcreteMutableAttributedString initWithString:: nil value
NSMutableAttributedString *attributedStr = [[NSMutableAttributedString alloc] initWithString: originStr];
NSInternalInconsistencyException:内部不一致异常/内部矛盾异常
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException',reason: 'Could not load NIB in bundle: 'NSBundle
NSArray*nib = [[NSBundle mainBundle] loadNibNamed:@"CellForOffers" owner:self options:nil];
loadNibNamed的xib不存在,出现NSInternalInconsistencyException错误
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException',reason: 'attempt to delete row 13 from section 0 which only contains 12 rows before the update'
- (void)insertRowsAtIndexPaths:(NSArray
*)indexPaths withRowAnimation:(UITableViewRowAnimation)animation; - (void)deleteRowsAtIndexPaths:(NSArray
*)indexPaths withRowAnimation:(UITableViewRowAnimation)animation; - (void)reloadRowsAtIndexPaths:(NSArray
*)indexPaths withRowAnimation:(UITableViewRowAnimation)animation API_AVAILABLE(ios(3.0)); - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath API_AVAILABLE(ios(5.0));
使用这4个方法要特别注意,它也数组一样容易越界,属于tableview的页面row越界
Invalid update: invalid number of rows in section 0.The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (3),plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted)and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).
这种情况一般是数据源变化了,操作的时候和之前的数据源不一致了,一般出现在一些业务场景异步处理数据源.比如消息,网络请求等
An instance 0x102471600 of class YYTextView was deallocated while key value observers were still registered with it. Current observation info:
( Context: 0x0, Property: 0x174c59740> ) @weakify(self)
[self.textInputView.textView addObserverBlockForKeyPath:@"contentSize" block:^(YYTextView *obj, id _Nullable oldVal, id _Nullable newVal) {
@strongify(self)
if (!self) return ;
}];
没写 [self.textInputView.textView removeObserverBlocks];ios11以下版本,会导致闪退
addObserverBlockForKeyPath却忘记移除,当这个类self回收了,再次触发监听就会出现找不到改对象而出现闪退.类似的监听还有通知、KVO都会导致闪退.需要注意的下在ios11以下都会闪退,亲测试ios11及以上不再会闪退,如果你的app需要兼容的系统版本是11及以上可以不需要写,但是为了研发规范和习惯,建议都写.
关于SIGSEGV
SIG 是信号名的通用前缀, SEGV 是 segmentation violation 的缩写
在 POSIX 兼容的平台上,SIGSEGV 是当一个进程执行了一个无效的内存引用,或发生段错误时发送给它的信号。SIGSEGV 的符号常量在头文件 signal.h 中定义。因为在不同平台上,信号数字可能变化,因此符号信号名被使用。通常,它是信号11。
闪退场景一:recorder deleteRecording 之前 先判断文件是否存在,否则会造成过度释放,解决方法:
if ([[NSFileManager defaultManager] fileExistsAtPath:self.recorder.url.path]) {
if (![self.recorder deleteRecording])
NSLog(@"Failed to delete %@", self.recorder.url);
}
闪退场景二: delegate = nil 。
将XXViewContrller设置为delegate时,当页面发生跳转时,XXViewController的对象会被释放,这是代码走到[_delegate callbackMethod],便出现crash。解决方法有二:1.将@property (nonatomic ,assign) id
闪退场景三:键盘和使用了method swizzle将NSArray和NSMutableArray的objectAtIndex:等其他类进行数据安全导致的
不管行为轨迹是如何的,在哪个页面闪退,最后的本质应该都是一样的.ios11以下版本,我们目前报过来的基本是10.3.3和少量的10.3.2、10.3.1 由于没有真机,我们用模拟机模拟,如果不开启Xcode的Zombie Objects,不是毕现的,很少机率偶发的.调试的时候可以开启Xcode的Zombie Objects,并找到能弹出键盘的界面中,键盘显示的情况下 home app 进入后台,再单击app 图标 切换回前台时 发生crash.
处理:Safe文件设置为挂钩NSMutableArray方法编译器标志的位置 -fno-objc-arc 或者不使用方法交换的方式来做safe处理.
Methodmethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"),@selector(objectAtIndex:));Method method2 = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(safeObjectAtIndex:));method_exchangeImplementations(method, method2);
去调类似代码,让开发人员规范开发不依赖safe类.
好了就写到这里了,这就是我们项目中发现的一些问题,后面有新的问题再来补充,或大家有啥发现也可以来补充.