iOS熱門崩潰分析

前言

隨著移動(dòng)互聯(lián)網(wǎng)的發(fā)展,各種App應(yīng)用已經(jīng)融入到我們的日常生活當(dāng)中,應(yīng)用的穩(wěn)定性的要求也越來越高,首當(dāng)其沖的就是應(yīng)用的crash問題,輕則影響用戶的良好體驗(yàn),重則導(dǎo)致用戶大量流失造成巨大的影響。所以解決crash是重要而緊急的事情。

2021年友盟+發(fā)布了移動(dòng)應(yīng)用性能體驗(yàn)報(bào)告,報(bào)告中指出App的整理崩潰率為0.29%,其中iOS端崩潰率為0.10%。以及熱門崩潰排行榜:


崩潰排行榜

本文將通過熱門崩潰產(chǎn)生的原因以及崩潰demo(寫bug)來了解這些崩潰是如何產(chǎn)生的。熱門崩潰占比是很高的,如果能解決這些熱門崩潰,移動(dòng)應(yīng)用的質(zhì)量會(huì)有很大的提升。

NSException

首先我們先來介紹一下NSException:


NSException

相信大家對(duì)這個(gè)頁面不會(huì)陌生吧,這個(gè)日志就是NSException產(chǎn)生的,一旦程序拋出異常,程序就會(huì)崩潰,控制臺(tái)就會(huì)輸出這些崩潰日志。

NSException對(duì)象繼承自NSObject,是專門用來拋出Objective-C異常的,有四個(gè)屬性:

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

當(dāng)出現(xiàn)異常時(shí),會(huì)拋出一個(gè)NSException對(duì)象,內(nèi)容如上圖所示。


異常

除了上面的屬性外,NSException還預(yù)定義了一些通用的異常名稱:

/***************    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這個(gè)異常就是定義在UIKit的UIApplication中的

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

當(dāng)然我們也可以使用自定義異常進(jìn)行拋出。

NSString *nilStr = nil;
    NSMutableArray *arrayM = [NSMutableArray array];
    @try {
        //如果@try中的代碼會(huì)導(dǎo)致程序崩潰,就會(huì)來到@catch
        
        //將一個(gè)nil插入到可變數(shù)組中,這行代碼肯定有問題
        [arrayM addObject:nilStr];
    }
    @catch (NSException *exception) {
        //在這里你可以進(jìn)行相應(yīng)的處理操作
        //異常的名稱
        NSString *exceptionName = @"異常的名稱";
        //異常的原因
        NSString *exceptionReason = @"我異常的原因";
        //異常的信息
        NSDictionary *exceptionUserInfo = nil;

        NSException *exception1 = [NSException exceptionWithName:exceptionName reason:exceptionReason userInfo:exceptionUserInfo];

        //拋異常
        @throw exception1;
    }
    @finally {
        //@finally中的代碼是一定會(huì)執(zhí)行的
        
        //你可以在這里進(jìn)行一些相應(yīng)的操作
    }

熱門崩潰

下面我們看一下這些熱門崩潰都是什么以及產(chǎn)生的原因

1,NSInvalidArgumentException

非法參數(shù)異常(NSInvalidArgumentException)是Objective-C代碼最常出現(xiàn)的錯(cuò)誤,所以平時(shí)寫代碼的時(shí)候,需要多加注意,加強(qiáng)對(duì)參數(shù)的檢查,避免傳入非法參數(shù)導(dǎo)致異常,其中尤以nil參數(shù)為甚。
(1)無法識(shí)別選擇器

UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    button.frame = CGRectMake(100, 100, 100, 100);
    button.backgroundColor = [UIColor redColor];
    // 未實(shí)現(xiàn)buttonAction
    [button addTarget:self action:@selector(buttonAction) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];

(2)數(shù)組中插入異常數(shù)據(jù),傳遞nil

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

(3)NSString在使用stringWithString時(shí),傳遞nil

NSString *str = [NSString stringWithString:nil];

(4)參數(shù)類型傳遞錯(cuò)誤

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

2,NSGenericException

通用異常
(1)foreach操作
NSGenericException這個(gè)異常容易出現(xiàn)在foreach操作中,在for in循環(huán)中如果修改所遍歷的數(shù)組,無論你是add或remove,都會(huì)出錯(cuò)

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操作請(qǐng)使用for循環(huán)。
(2)讀取文件失敗

3,NSRangeException

越界異常(NSRangeException)是iOS開發(fā)中比較常出現(xiàn)的異常
(1)容器越界

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

在使用tableview或者collectionview時(shí)數(shù)據(jù)源容器越界
(2)處理數(shù)據(jù)范圍NSRange超過數(shù)據(jù)本身的長(zhǎng)度

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的發(fā)生,必須傳入的下標(biāo)參數(shù)或者NSRange范圍進(jìn)行合法性檢查,判斷是否在集合數(shù)據(jù)的范圍內(nèi),然后再進(jìn)行相關(guān)的處理
(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

這是內(nèi)存不足的問題,無法分配足夠的內(nèi)存空間,比如需要分配的內(nèi)存大小是一個(gè)不正常的值,比較巨大或者設(shè)備的內(nèi)存空間不足以及耗盡
(1)分配空間過大

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

(2)圖像占用空間過大

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

如果imageData長(zhǎng)度過長(zhǎng),就會(huì)出現(xiàn)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

這種情況一般是程序陷入死循環(huán),注意檢查代碼
解決辦法:對(duì)于程序中分配內(nèi)存空間的操作,需要檢查參數(shù)(空間大?。┑挠行?,特別是這個(gè)參數(shù)來自其他模塊的返回值,更應(yīng)該注意。

5,NSInternalInconsistencyException

內(nèi)部不一致異常(NSInternalInconsistencyException)

(1)NSMutableDictionary的錯(cuò)誤使用:比如把NSDictionary當(dāng)做NSMutableDictionary來使用,從他們內(nèi)部機(jī)理來說,就會(huì)產(chǎn)生一些錯(cuò)誤,NSMutableDictionary中有很多NSDictionary不支持的接口。

(2)界面使用不當(dāng):如在子線程刷新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。方法,判斷當(dāng)前線程是否為主線程,如果不是,在主線程執(zhí)行。

(3)tableview里再cellForRowAtIndexPath方法中,返回的內(nèi)容不是UITableViewCell類型,比如返回了nil

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

(4)KVO的observer沒有實(shí)現(xiàn)observeValueForKeyPath方法

[self.titleLabel addObserver:self forKeyPath:@"backgroundColor" options:NSKeyValueObservingOptionNew context:nil]; self.titleLabel.backgroundColor = [UIColor blueColor]; // 未實(shí)現(xiàn)observeValueForKeyPath

6,UIApplicationInvalidInterfaceOrientation

應(yīng)用程序無效界面定向異常

(1)ViewController中設(shè)置的方位跟應(yīng)用支持的方位不一致:應(yīng)用只支持豎屏,VC卻支持橫屏

蘋果目前已經(jīng)對(duì)這種情況做了兼容,如果應(yīng)用只支持豎屏,而VC支持橫屏的情況只會(huì)橫屏無效果,并不會(huì)crash了。

7,CALayerInvalidGeometry

CALayer無效坐標(biāo)異常

(1)rect里面包含非數(shù)字

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

(2)rect中在計(jì)算時(shí)分母為0

蘋果目前已經(jīng)對(duì)這種情況做了兼容,如果分母為0則會(huì)報(bào)一個(gè)警告,并且該值當(dāng)做0來處理

坐標(biāo)異常

8,NSFilehandleOperationException

手機(jī)空間不足,會(huì)使客戶端直接崩潰,觸發(fā)NSFilehandleOperationException,所以在處理文件時(shí),比如應(yīng)用頻繁的保存文檔,緩存資料或者處理比較大的數(shù)據(jù),需要考慮空間的問題

(1)沒有空間:手機(jī)沒有存儲(chǔ)空間了,或者需要寫的文件太大,會(huì)觸發(fā)“No space left on device”異常

(2)文件讀寫權(quán)限:明明是要寫文件,可只給了讀權(quán)限,所以觸發(fā)了“Bad file descriptor”異常

(3)讀文件失?。?- readDataOfLength:

(4)獲取文件數(shù)據(jù)失敗

解決辦法:在處理文件I/O時(shí),需要考慮到存儲(chǔ)空間的有限性,對(duì)大小參數(shù)進(jìn)行有效性校驗(yàn);另外對(duì)NSFileHandle的有效性也要判斷。

9,NSUnknownKeyException

未知key異常

(1)不符合鍵值編碼

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

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

10,NSArchiverArchiveInconsistency

存檔不一致異常

總結(jié)

這些熱門崩潰是占比較大的崩潰,可以針對(duì)這些常見的、熱門的崩潰進(jìn)行防護(hù),然后再輔助crash上報(bào)以及日志等功能來保證App應(yīng)用的穩(wěn)定性。

當(dāng)然除了這些熱門崩潰還會(huì)有很多偶現(xiàn)的、不好定位的崩潰,需要花更多的精力來解決。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 沒想到都2021年,我還得寫篇文章來講講 Crash 監(jiān)聽的一些事情。雖然蠻多文章講 Crash 監(jiān)聽這塊,但總是...
    felix9閱讀 14,093評(píng)論 1 49
  • 這篇文章其實(shí)想探討一下 crash 都有哪些種類,以及如何解決醬紫,感覺自己之前好像有淺談過log(https:/...
    木小易Ying閱讀 1,376評(píng)論 1 8
  • 崩潰分析 崩潰日志(crash log) 根據(jù)符號(hào)表來監(jiān)測(cè)崩潰位置 什么是符號(hào)表符號(hào)表就是指在Xcode項(xiàng)目編譯后...
    紙簡(jiǎn)書生閱讀 5,948評(píng)論 0 17
  • 在iOS開發(fā)中經(jīng)常需要靠記錄日志來調(diào)試應(yīng)用程序、解決崩潰問題等,整理常用的日志輸出和崩潰日志分析。最新更新:201...
    就叫yang閱讀 6,542評(píng)論 1 63
  • 1. 異常的類型 Mach異常:是指最底層的內(nèi)核級(jí)異常。用戶態(tài)?的開發(fā)者可以直接通過Mach API設(shè)置threa...
    沉江小魚閱讀 1,591評(píng)論 2 9

友情鏈接更多精彩內(nèi)容