iOS應(yīng)用Crash保護(hù)系統(tǒng)

源碼地址

開發(fā)過程中膝但,即使我們很注意的去寫代碼,但是還是不能百分百的保證避免程序的Crash汗销;iOS應(yīng)用Crash保護(hù)系統(tǒng) 的設(shè)計初衷,就是降低APP的崩潰率抵窒。利用Objective-C語言的動態(tài)特性弛针,采用面向切面編程的設(shè)計思想,做到無痕植入李皇。能夠自動在APP運行時實時捕獲導(dǎo)致APP崩潰的原因削茁,然后通過特定的技術(shù)手段去解決這些問題,使APP免于崩潰掉房,繼續(xù)運行茧跋,為APP的持續(xù)運轉(zhuǎn)保駕護(hù)航。


功能簡介

iOS應(yīng)用Crash保護(hù)系統(tǒng) 計劃解決程序運行過程中的大部分崩潰卓囚,但也有一些比較難發(fā)生的崩潰沒有找到具體原因和解決方案瘾杭,該方案主要從以下幾個方面進(jìn)行處理:

  • unrecognized selector引起的崩潰
  • 容器類數(shù)據(jù)類型操作引起的崩潰
  • 字符串操作引起的崩潰
  • KVO引起的崩潰
  • NSTimer引起的崩潰
  • 非主線程刷新UI
  • 野指針
  • NSNotification引起的崩潰

實現(xiàn)原理

unrecognized selector引起的崩潰的防護(hù)

unrecognized selector的崩潰在APP中占了很大比例,具體造成原因通常是:一個對象調(diào)用一個自己沒有實現(xiàn)的方法造成的哪亿;
例如:

NSObject *obj = [NSObject new];
[obj methodNoRealize];

具體錯誤原因如下:
-[obj methodNoRealize]: unrecognized selector sent to instance 0x60000087xxxxx

要解決該類問題粥烁,我們可以從OC消息轉(zhuǎn)發(fā)的過程中找到答案;首先看一下方法調(diào)用和消息轉(zhuǎn)發(fā)流程:
當(dāng)對象obj調(diào)用方法methodNoRealize時蝇棉,會執(zhí)行以下步驟
1.首先讨阻,在obj對應(yīng)類的緩存方法列表中找methodNoRealize,如果找到篡殷,轉(zhuǎn)向相應(yīng)實現(xiàn)并執(zhí)行变勇;
2.如果沒找到,在obj的方法列表中找methodNoRealize,如果找到搀绣,轉(zhuǎn)向相應(yīng)實現(xiàn)執(zhí)行;
3.如果沒找到戳气,去父類指針?biāo)赶虻膶ο笾袌?zhí)行1链患,2;
4.以此類推瓶您,如果一直到根類還沒找到麻捻,轉(zhuǎn)向攔截調(diào)用,走消息轉(zhuǎn)發(fā)機制呀袱;

消息轉(zhuǎn)發(fā)機制如下圖:

消息轉(zhuǎn)發(fā)流程.jpg

在消息轉(zhuǎn)發(fā)流程中贸毕,有三次機會可以“拯救”沒有實現(xiàn)的方法引起的崩潰,分別再消息轉(zhuǎn)發(fā)的三步流程中夜赵,我們可以通過HOOK這三步的方法實現(xiàn)對該類崩潰的保護(hù)明棍;
1.+ (BOOL)resolveInstanceMethod:(SEL)selobj找不到methodNoRealize之后,最先執(zhí)行該方法寇僧,此方法返回值是BOOL,沒有找到就是NO,找到就返回YES,
在此方法中解決的方法:在obj的類中加入methodNoRealize,并綁定方法實現(xiàn)摊腋,具體操作如下:

+ (BOOL)resolveInstanceMethod:(SEL)sel
{    
    NSMethodSignature* sign = [self methodSignatureForSelector:sel];
    if (!sign) {
        class_addMethod([self class], sel, (IMP)unrecognizedSelector, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
- (void)unrecognizedSelector
{
    // do something
}

此步操作可以解決該問題,但是會在類中添加一個方法嘁傀,在開發(fā)過程中兴蒸,往往不是因為自己本類方法沒有實現(xiàn)引起這種崩潰,而是對象類型判斷錯誤導(dǎo)致细办,這樣就會給未知的類中添加一個方法橙凳,對類造成污染;故笑撞,在這里解決可行岛啸,但不是最佳的方式;
2.- (id)forwardingTargetForSelector:(SEL)aSelector:當(dāng)?shù)谝徊椒祷亟Y(jié)果為NO時娃殖,就會走到該方法中值戳,在這個方法中,可以將obj查找不到的方法轉(zhuǎn)發(fā)到另外一個對象中去炉爆,在另外對象中進(jìn)行處理堕虹;具體操作如下:

- (id)safeForwardingTargetForSelector:(SEL)aSelector
{
    NSMethodSignature *methodSignature = [self methodSignatureForSelector:aSelector];
    if (!methodSignature) {
        
        id obj  = [[HYUnrecognizedSelectorHandle alloc] init];
        IMP imp = class_getMethodImplementation([HYUnrecognizedSelectorHandle class], @selector(unrecognizedSelector));
        class_addMethod([obj class], aSelector, imp, "v@:");
        return obj;
    }
    
    return [self safeForwardingTargetForSelector:aSelector];
}

在此步中,可以實例化一個預(yù)先寫好的類的對象HYUnrecognizedSelectorHandle,然后獲取該類的unrecognizedSelector方法的實現(xiàn)芬首,將該實現(xiàn)赴捞,綁定給該類的名稱為aSelector的方法,然后將該對象返回郁稍,這樣我們就可以再unrecognizedSelector統(tǒng)一處理該種錯誤赦政;
這種方式是最多的實現(xiàn)方式,因為既不污染對象對應(yīng)的類,又比下一步處理的時候消耗要小恢着,但是在這種方式中存在一個問題桐愉,在經(jīng)過測試發(fā)現(xiàn),在調(diào)用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector方法過程中掰派,某些類遵循了一些協(xié)議从诲,但是沒有實現(xiàn)協(xié)議方法的時候,該方法也會返回為方法簽名靡羡,這樣就會跳過將未實現(xiàn)方法轉(zhuǎn)嫁給另外一個類的步驟系洛,就不能實現(xiàn)保護(hù)功能;也就是說:某些類遵循了一些協(xié)議略步,但是沒有實現(xiàn)協(xié)議方法的時候描扯,在此步的解決方案不能生效
3.- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- (void)forwardInvocation:(NSInvocation *)anInvocation:在此步中,有三種情況需要處理趟薄,詳細(xì)如下:


- (NSMethodSignature *)safeMethodSignatureForSelector:(SEL)aSelector
{
    NSMethodSignature *methodSignature = [self safeMethodSignatureForSelector:aSelector];
    if (methodSignature) return methodSignature;

    
    IMP originIMP       = class_getMethodImplementation([NSObject class], @selector(methodSignatureForSelector:));
    IMP currentClassIMP = class_getMethodImplementation([self class],     @selector(methodSignatureForSelector:));
    // 如果子類重載了該方法绽诚,則返回nil
    if (originIMP != currentClassIMP) return nil;

    
    // - (void)xxxx
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

- (void)safeForwardInvocation:(NSInvocation *)invocation
{
    NSString *reason = [NSString stringWithFormat:@"class:[%@] not found selector:(%@)",NSStringFromClass(self.class),NSStringFromSelector(invocation.selector)];

    NSException *exception = [NSException exceptionWithName:@"Unrecognized Selector"
                                                     reason:reason
                                                   userInfo:nil];
    // 收集錯誤信息
    hy_handleErrorWithException(exception);

}

在以上代碼中,- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector 處理了三種情況:
1.如果有方法簽名竟趾,則正常流程憔购;
2.如果沒有方法簽名,則返回一個默認(rèn)的方法簽名岔帽,然后在- (void)forwardInvocation:(NSInvocation *)anInvocation中處理玫鸟;
3.如果子類重載了該方法,則返回nil犀勒,具體處理交給子類屎飘;

綜上所述,unrecognized selector引起的崩潰贾费,在第三步中處理為最佳實踐方案钦购,此方案雖然相對比第二步中處理會效率略低,但可以解決第二步中解決不了的問題褂萧;

容器類數(shù)據(jù)類型操作引起的崩潰

數(shù)組押桃、字典、集合等是我們開發(fā)中經(jīng)常使用的數(shù)據(jù)類型导犹,在使用中經(jīng)常會出現(xiàn)數(shù)組越界唱凯、字典插入空值、集合越界等錯誤引起的崩潰谎痢;在開發(fā)中這種崩潰可以及時提醒我們改正錯誤磕昼,但是如果在線上也因為這種錯誤引起崩潰,對用戶來說节猿,體驗是很不友好的票从;
對于這種崩潰的保護(hù),采用的方法是:對容易出現(xiàn)異常的方法進(jìn)行HOOK,然后再自定義實現(xiàn)中峰鄙,對異常進(jìn)行處理浸间,并對異常進(jìn)行收集、上報吟榴;
對這三種數(shù)據(jù)類型发框,目前對常用的方法進(jìn)行了HOOK,做了異常保護(hù)煤墙,后續(xù)可以對所有有可能發(fā)生異常的方法進(jìn)行HOOK;

數(shù)組:

+ (instancetype)arrayWithObject:(id)anObject;
- (id)objectAtIndex:(NSUInteger)index;
- (id)objectAtIndexedSubscript:(NSInteger)index;
- (NSArray *)subarrayWithRange:(NSRange)range;
+ (instancetype)arrayWithObjects:(const id [])objects count:(NSUInteger)cnt;
- (void)addObject:(id)anObject;
- (id)objectAtIndex:(NSUInteger)index;
- (id)objectAtIndexedSubscript:(NSInteger)index;
- (void)insertObject:(id)anObject atIndex:(NSUInteger)index;
- (void)removeObjectAtIndex:(NSUInteger)index;
- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject;
- (void)removeObjectsInRange:(NSRange)range;
- (NSArray *)subarrayWithRange:(NSRange)range;

字典:

+ (instancetype)dictionaryWithObject:(ObjectType)object forKey:(KeyType <NSCopying>)key;
+ (instancetype)dictionaryWithObjects:(const ObjectType _Nonnull [_Nullable])objects forKeys:(const KeyType <NSCopying> _Nonnull [_Nullable])keys count:(NSUInteger)cnt;
- (instancetype)initWithObjectsAndKeys:(id)firstObject, ... ;
- (instancetype)initWithObjects:(NSArray<ObjectType> *)objects forKeys:(NSArray<KeyType <NSCopying>> *)keys;
- (void)setObject:(ObjectType)anObject forKey:(KeyType <NSCopying>)aKey;
- (void)setObject:(nullable ObjectType)obj forKeyedSubscript:(KeyType <NSCopying>)key ;
- (void)removeObjectForKey:(KeyType)aKey;

集合:

+ (instancetype)setWithObject:(ObjectType)object;
- (void)addObject:(ObjectType)object;
- (void)removeObject:(ObjectType)object;

字符串操作引起的崩潰

字符串是我們開發(fā)中使用場景最多的數(shù)據(jù)類型宪拥,對字符串進(jìn)行操作也是很容易引起崩潰仿野;例如:字符串截取、字符串拼接她君、刪除指定范圍內(nèi)子串脚作、判斷是否包含子串等,如果開發(fā)中不注意 很容易引起程序崩潰缔刹;
對于這種崩潰的保護(hù)球涛,我們采用和容器類數(shù)據(jù)類型相似的方法:對容易出現(xiàn)異常的方法進(jìn)行HOOK,然后再自定義實現(xiàn)中校镐,對異常進(jìn)行處理亿扁,并對異常進(jìn)行收集、上報鸟廓;

KVO引起的崩潰

KVO:即Key-Value Observing从祝,它提供一種機制,當(dāng)指定的對象的屬性被修改后引谜,則對象的監(jiān)聽者就會接受收到通知牍陌。簡單的說就是每次指定的被觀察的對象的屬性被修改后,KVO就會自動通知相應(yīng)的觀察者了员咽。

KVO機制在iOS的很多開發(fā)場景中都會被使用到毒涧。不過如果一不小心使用不當(dāng)?shù)脑挘瑫?dǎo)致Crash問題贝室。KVO引起的Crash主要包含以下兩個方面:

  • KVO的被觀察者dealloc時仍然注冊著KVO導(dǎo)致的Crash契讲;
  • KVO重復(fù)添加觀察者或重復(fù)移除;

針對以上問題档玻,采用以下解決方案:
由于絕大部分的問題都是因為KVO監(jiān)聽對象屬性過多造成的混亂怀泊,導(dǎo)致在開發(fā)過程中不能很好的手動管理,那么就可以給被觀察對象綁定一個Map误趴,這個Map的作用是存儲管理該對象被觀察的屬性霹琼,用它來維護(hù)對象的被觀察屬性的移除和添加;這樣做的好處有以下兩點:

  • 如果是非正常的添加或者刪除觀察者,就可以通過Map的存儲判斷出異常枣申,從而避免這種操作售葡;
  • 被觀察者在dealloc之前都會銷毀關(guān)聯(lián)的對象,這時該Map也被自動銷毀(系統(tǒng)特性忠藤,對象銷毀的時候挟伙,會檢查和該對象關(guān)聯(lián)的對象,然后銷毀)模孩,避免對象銷毀時尖阔,還注冊者觀察者;

NSTimer引起的崩潰

開發(fā)時榨咐,我們不免使用定時器介却,在使用以下方法時:

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

定時器會對target施加一個強引用,如果不在適當(dāng)時機對timer進(jìn)行invalidate块茁,則會出現(xiàn)內(nèi)存泄露齿坷;假如當(dāng)對象銷毀之后,還沒有對定時器進(jìn)行invalidate数焊,則在某種情況下永淌,也會引起崩潰;具體情況和selector內(nèi)部實現(xiàn)有關(guān)佩耳;

在這里遂蛀,對NSTimer的處理為,引入中間代理對象TimerTagetAgent蚕愤,解除timertarget之間的循環(huán)引用答恶;結(jié)構(gòu)如下:

Timer.jpg

這樣處理之后,解除掉了timertarget的強引用萍诱,并且可以在TimerTagetAgent中對timer進(jìn)行適時的invalidate掉悬嗓,這樣就解決了內(nèi)存泄露和不確定性閃退問題,并且可以上報錯誤裕坊,督促開發(fā)人員改正包竹;

非主線程刷新UI

在非主線程刷新UI操作會導(dǎo)致界面不能按照想要的結(jié)果展示,而且很有可能造成崩潰籍凝;在這里處理方法為:HOOK以下下三個系統(tǒng)方法周瞎,在debugrelease模式下做不同操作;

- (void)setNeedsLayout;
- (void)setNeedsDisplay;
- (void)setNeedsDisplayInRect:(CGRect)rect;

debug模式下:使用斷言機制饵蒂,是程序進(jìn)行崩潰声诸,并輸出錯誤信息,促使開發(fā)人員修改問題退盯;
release模式下:異步到主線程刷新UI
這里和之前類型的崩潰采用的方法不同彼乌,是在開發(fā)環(huán)境下是程序主動崩潰泻肯,促使開發(fā)人員解決問題,在生產(chǎn)環(huán)境下采用崩潰保護(hù)慰照,但是沒有上報異常灶挟,原因為:這三個方法刷新UI的操作時在RunLoop的每個時鐘周期都會操作,如果上報異常毒租,服務(wù)器將會收到大量不必要信息稚铣;

野指針

NSNotification引起的崩潰

對于iOS 9以下需要做操作,但由于9以下系統(tǒng)較少墅垮,此模塊后續(xù)完善惕医;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市算色,隨后出現(xiàn)的幾起案子曹锨,更是在濱河造成了極大的恐慌,老刑警劉巖剃允,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異齐鲤,居然都是意外死亡斥废,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門给郊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來牡肉,“玉大人,你說我怎么就攤上這事淆九⊥炒福” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵炭庙,是天一觀的道長饲窿。 經(jīng)常有香客問我,道長焕蹄,這世上最難降的妖魔是什么逾雄? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮腻脏,結(jié)果婚禮上鸦泳,老公的妹妹穿的比我還像新娘。我一直安慰自己永品,他們只是感情好做鹰,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著鼎姐,像睡著了一般钾麸。 火紅的嫁衣襯著肌膚如雪更振。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天喂走,我揣著相機與錄音殃饿,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的资盅。 我是一名探鬼主播婚惫,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼萤悴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤肴甸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后囚巴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體原在,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年彤叉,在試婚紗的時候發(fā)現(xiàn)自己被綠了庶柿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡秽浇,死狀恐怖浮庐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情柬焕,我是刑警寧澤审残,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站斑举,受9級特大地震影響搅轿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜富玷,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一介时、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧凌彬,春花似錦沸柔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至伐蒋,卻和暖如春工三,著一層夾襖步出監(jiān)牢的瞬間迁酸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工俭正, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留奸鬓,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓掸读,卻偏偏與公主長得像串远,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子儿惫,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉澡罚,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,679評論 0 9
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,089評論 1 32
  • 面向?qū)ο蟮娜筇匦裕悍庋b、繼承肾请、多態(tài) OC內(nèi)存管理 _strong 引用計數(shù)器來控制對象的生命周期留搔。 _weak...
    運氣不夠技術(shù)湊閱讀 1,085評論 0 10
  • 面試題參考1 : 面試題[http://www.cocoachina.com/ios/20150803/12872...
    江河_ios閱讀 1,710評論 0 4
  • 最近在做課程設(shè)計,每天挺忙的铛铁,很多時候都沒來得及看朋友過我發(fā)的消息隔显,很久之后才回復(fù)朋友,他和我調(diào)侃說“看來你...
    念你有時閱讀 416評論 0 1