iOS热门崩溃分析

作者: 好_好先生 | 来源:发表于2021-11-22 10:21 被阅读0次

    前言

    随着移动互联网的发展,各种App应用已经融入到我们的日常生活当中,应用的稳定性的要求也越来越高,首当其冲的就是应用的crash问题,轻则影响用户的良好体验,重则导致用户大量流失造成巨大的影响。所以解决crash是重要而紧急的事情。

    2021年友盟+发布了移动应用性能体验报告,报告中指出App的整理崩溃率为0.29%,其中iOS端崩溃率为0.10%。以及热门崩溃排行榜:


    崩溃排行榜

    本文将通过热门崩溃产生的原因以及崩溃demo(写bug)来了解这些崩溃是如何产生的。热门崩溃占比是很高的,如果能解决这些热门崩溃,移动应用的质量会有很大的提升。

    NSException

    首先我们先来介绍一下NSException:


    NSException

    相信大家对这个页面不会陌生吧,这个日志就是NSException产生的,一旦程序抛出异常,程序就会崩溃,控制台就会输出这些崩溃日志。

    NSException对象继承自NSObject,是专门用来抛出Objective-C异常的,有四个属性:

    • name:异常名称
    • reason:异常原因
    • userInfo:异常信息,字典形式
    • reserved:堆栈信息
    @interface NSException : NSObject <NSCopying, NSSecureCoding> {
        @private
        NSString        *name;
        NSString        *reason;
        NSDictionary    *userInfo;
        id          reserved;
    }
    

    当出现异常时,会抛出一个NSException对象,内容如上图所示。


    异常

    除了上面的属性外,NSException还预定义了一些通用的异常名称:

    /***************    Generic Exception names     ***************/
    /*
    You should typically use a more specific exception name.
    */
    FOUNDATION_EXPORT NSExceptionName const NSGenericException; 
    /*
    Name of an exception that occurs when attempting to access outside the bounds of some data, such as beyond the end of a string.
    */
    FOUNDATION_EXPORT NSExceptionName const NSRangeException; 
    /*
    Name of an exception that occurs when you pass an invalid argument to a method, such as a nil pointer where a non-nil object is required.
    */
    FOUNDATION_EXPORT NSExceptionName const NSInvalidArgumentException;
    /*
    Name of an exception that occurs when an internal assertion fails and implies an unexpected condition within the called code.
    */ 
    FOUNDATION_EXPORT NSExceptionName const NSInternalInconsistencyException; 
    
    /*
    Obsolete; not currently used.
    */
    FOUNDATION_EXPORT NSExceptionName const NSMallocException; 
    
    /*
    Name of an exception that occurs when a remote object is accessed from a thread that should not access it.
    */
    FOUNDATION_EXPORT NSExceptionName const NSObjectInaccessibleException; 
    /*
    Name of an exception that occurs when the remote side of the NSConnection refused to send the message to the object because the object has never been vended.
    */
    FOUNDATION_EXPORT NSExceptionName const NSObjectNotAvailableException; 
    /*
    Name of an exception that occurs when an internal assertion fails and implies an unexpected condition within the distributed objects.
    */
    FOUNDATION_EXPORT NSExceptionName const NSDestinationInvalidException;
        
        /*
    Name of an exception that occurs when a timeout set on a port expires during a send or receive operation.
    */
    FOUNDATION_EXPORT NSExceptionName const NSPortTimeoutException;
    /*
    Name of an exception that occurs when the send port of an NSConnection has become invalid.
    */
    FOUNDATION_EXPORT NSExceptionName const NSInvalidSendPortException;
    /*
    Name of an exception that occurs when the receive port of an NSConnection has become invalid.
    */
    FOUNDATION_EXPORT NSExceptionName const NSInvalidReceivePortException;
    /*
    Generic error occurred on send.
    */
    FOUNDATION_EXPORT NSExceptionName const NSPortSendException;
    /*
    Generic error occurred on receive.
    */
    FOUNDATION_EXPORT NSExceptionName const NSPortReceiveException;
    
    
    /*
    No longer used.
    */
    FOUNDATION_EXPORT NSExceptionName const NSOldStyleException;
    
    /*
    The name of an exception raised by NSArchiver if there are problems initializing or encoding.
    */
    FOUNDATION_EXPORT NSExceptionName const NSInconsistentArchiveException;
    
    /***************    Exception object    ***************/
    

    但并不是所有的异常都在这里定义,如UIApplicationInvalidInterfaceOrientation这个异常就是定义在UIKit的UIApplication中的

    UIKIT_EXTERN NSExceptionName const UIApplicationInvalidInterfaceOrientationException API_AVAILABLE(ios(6.0)) API_UNAVAILABLE(tvos);
    

    当然我们也可以使用自定义异常进行抛出。

    NSString *nilStr = nil;
        NSMutableArray *arrayM = [NSMutableArray array];
        @try {
            //如果@try中的代码会导致程序崩溃,就会来到@catch
            
            //将一个nil插入到可变数组中,这行代码肯定有问题
            [arrayM addObject:nilStr];
        }
        @catch (NSException *exception) {
            //在这里你可以进行相应的处理操作
            //异常的名称
            NSString *exceptionName = @"异常的名称";
            //异常的原因
            NSString *exceptionReason = @"我异常的原因";
            //异常的信息
            NSDictionary *exceptionUserInfo = nil;
    
            NSException *exception1 = [NSException exceptionWithName:exceptionName reason:exceptionReason userInfo:exceptionUserInfo];
    
            //抛异常
            @throw exception1;
        }
        @finally {
            //@finally中的代码是一定会执行的
            
            //你可以在这里进行一些相应的操作
        }
    

    热门崩溃

    下面我们看一下这些热门崩溃都是什么以及产生的原因

    1,NSInvalidArgumentException

    非法参数异常(NSInvalidArgumentException)是Objective-C代码最常出现的错误,所以平时写代码的时候,需要多加注意,加强对参数的检查,避免传入非法参数导致异常,其中尤以nil参数为甚。
    (1)无法识别选择器

    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
        button.frame = CGRectMake(100, 100, 100, 100);
        button.backgroundColor = [UIColor redColor];
        // 未实现buttonAction
        [button addTarget:self action:@selector(buttonAction) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:button];
    

    (2)数组中插入异常数据,传递nil

    NSMutableArray *array = [NSMutableArray array];
    [array addObject:nil];
    

    (3)NSString在使用stringWithString时,传递nil

    NSString *str = [NSString stringWithString:nil];
    

    (4)参数类型传递错误

    UITextField *textField = [[UITextField alloc] init];
        textField.background = [UIColor blueColor];
    

    2,NSGenericException

    通用异常
    (1)foreach操作
    NSGenericException这个异常容易出现在foreach操作中,在for in循环中如果修改所遍历的数组,无论你是add或remove,都会出错

    NSArray *array = @[@"111", @"222", @"333", @"444", @"555"];
        NSMutableArray *marray = [array mutableCopy];
        for (NSString *item in marray) {
    //        [marray addObject:@"666"];
            if ([item isEqualToString:@"111"]) {
                [marray removeObject:item];
            }
        }
    

    解决办法:如果有add或remove操作请使用for循环。
    (2)读取文件失败

    3,NSRangeException

    越界异常(NSRangeException)是iOS开发中比较常出现的异常
    (1)容器越界

    NSArray *arry = @[@"111", @"222", @"333"];
        NSString *str = arry[4];
    

    在使用tableview或者collectionview时数据源容器越界
    (2)处理数据范围NSRange超过数据本身的长度

    NSDictionary *attributes = @{NSFontAttributeName:[UIFont fontWithName:@"PingFangSC-Regular" size:14],
                                     NSForegroundColorAttributeName:[UIColor colorWithRed:0.2 green:0.2 blue:0.188 alpha:1]
                                     };
        NSMutableAttributedString *mutableAttributedString = [[NSMutableAttributedString alloc] initWithString:@"123456" attributes:attributes];
        NSRange range = {1,8};
        [mutableAttributedString setAttributes:attributes range:range];
    

    解决办法:为了避免NSRangeException的发生,必须传入的下标参数或者NSRange范围进行合法性检查,判断是否在集合数据的范围内,然后再进行相关的处理
    (3)KVO被移除多次

    [self.titleLabel addObserver:self forKeyPath:@"backgroundColor" options:NSKeyValueObservingOptionNew context:nil];
        self.titleLabel.backgroundColor = [UIColor blueColor];
        //第一次remove
        [self.titleLabel removeObserver:self forKeyPath:@"backgroundColor"];
        //第二次remove
        [self.titleLabel removeObserver:self forKeyPath:@"backgroundColor"];
    

    4,NSMallocException

    这是内存不足的问题,无法分配足够的内存空间,比如需要分配的内存大小是一个不正常的值,比较巨大或者设备的内存空间不足以及耗尽
    (1)分配空间过大

    NSMutableData *data = [[NSMutableData alloc] initWithCapacity:1];
    NSInteger len = 203293514200000000;
    [data increaseLengthBy:len];
    

    (2)图像占用空间过大

    -[SDImageCache storeImage:recalculateFromImage:imageData:forKey:toDisk:]
    

    如果imageData长度过长,就会出现NSMallocException
    (3)OOM问题

    Terminating app due to uncaught exception 'NSMallocException', reason: 'Out of memory. We suggest restarting the application. If you have an unsaved document, create a backup copy in Finder, then try to save
    

    这种情况一般是程序陷入死循环,注意检查代码
    解决办法:对于程序中分配内存空间的操作,需要检查参数(空间大小)的有效性,特别是这个参数来自其他模块的返回值,更应该注意。

    5,NSInternalInconsistencyException

    内部不一致异常(NSInternalInconsistencyException)

    (1)NSMutableDictionary的错误使用:比如把NSDictionary当做NSMutableDictionary来使用,从他们内部机理来说,就会产生一些错误,NSMutableDictionary中有很多NSDictionary不支持的接口。

    (2)界面使用不当:如在子线程刷新UI

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ self.tableView.frame = CGRectMake(0, 0, 10, 0); });
    

    解决办法:通过runtime的方法替换,替换UIView 的 setNeedsLayout, layoutIfNeeded,layoutSubviews, setNeedsUpdateConstraints。方法,判断当前线程是否为主线程,如果不是,在主线程执行。

    (3)tableview里再cellForRowAtIndexPath方法中,返回的内容不是UITableViewCell类型,比如返回了nil

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { return nil; }
    

    (4)KVO的observer没有实现observeValueForKeyPath方法

    [self.titleLabel addObserver:self forKeyPath:@"backgroundColor" options:NSKeyValueObservingOptionNew context:nil]; self.titleLabel.backgroundColor = [UIColor blueColor]; // 未实现observeValueForKeyPath
    

    6,UIApplicationInvalidInterfaceOrientation

    应用程序无效界面定向异常

    (1)ViewController中设置的方位跟应用支持的方位不一致:应用只支持竖屏,VC却支持横屏

    苹果目前已经对这种情况做了兼容,如果应用只支持竖屏,而VC支持横屏的情况只会横屏无效果,并不会crash了。

    7,CALayerInvalidGeometry

    CALayer无效坐标异常

    (1)rect里面包含非数字

    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; button.frame = CGRectMake(100, 100, 100, NAN);
    

    (2)rect中在计算时分母为0

    苹果目前已经对这种情况做了兼容,如果分母为0则会报一个警告,并且该值当做0来处理

    坐标异常

    8,NSFilehandleOperationException

    手机空间不足,会使客户端直接崩溃,触发NSFilehandleOperationException,所以在处理文件时,比如应用频繁的保存文档,缓存资料或者处理比较大的数据,需要考虑空间的问题

    (1)没有空间:手机没有存储空间了,或者需要写的文件太大,会触发“No space left on device”异常

    (2)文件读写权限:明明是要写文件,可只给了读权限,所以触发了“Bad file descriptor”异常

    (3)读文件失败: - readDataOfLength:

    (4)获取文件数据失败

    解决办法:在处理文件I/O时,需要考虑到存储空间的有限性,对大小参数进行有效性校验;另外对NSFileHandle的有效性也要判断。

    9,NSUnknownKeyException

    未知key异常

    (1)不符合键值编码

    (2)kvc使用了不存在的key

    [self.parentVC setValue:@"123" forKey:@"abc"];
    

    10,NSArchiverArchiveInconsistency

    存档不一致异常

    总结

    这些热门崩溃是占比较大的崩溃,可以针对这些常见的、热门的崩溃进行防护,然后再辅助crash上报以及日志等功能来保证App应用的稳定性。

    当然除了这些热门崩溃还会有很多偶现的、不好定位的崩溃,需要花更多的精力来解决。

    相关文章

      网友评论

        本文标题:iOS热门崩溃分析

        本文链接:https://www.haomeiwen.com/subject/dmretrtx.html