大白健康系統--iOS APP運行時Crash自動修復系統

2022-01-30 網易杭州前端技術部
前言

大白(Baymax),迪士尼動畫《超能陸戰隊》中的健康機器人,是一個體型胖胖的充氣機器人,因呆萌的外表和善良的本質獲得大家的喜愛,被稱為「萌神」。

Baymax項目是為了減少開發人員在開發中一些不規範的代碼編寫造成的內存洩露,界面卡頓,耗電等問題而來的一個監控系統。

現在Baymax迎來了它新的功能:APP運行時Crash自動防護功能,為app的流程順利運行保駕護航!

下面將詳細介紹一下 APP運行時Crash自動修復系統 開發的目的,設計的原理以及使用的方法。

APP運行時Crash自動修復系統Chapter 1 - 開發目的

是否存在這樣的夜晚,當剛剛躺下準備美美的睡一覺的時候, 突然來一記奪命電話Call,一接起來發現是你老闆!!!「小王啊,剛剛上線的X.X.X版本出問題了啊,怎麼樣操作會crash啊,導致新功能都無法使用了,快定位一下是什麼原因,抓緊hotpatch修復一下啊!」。心裡一萬頭草泥馬呼嘯而過,瞬間已經滿頭大汗的你卻還要故作鎮靜地回答:「嗯,老闆我馬上去看看,一定努力解決問題!」 急忙打開電腦的你,知道今夜註定無眠了。

是否又存在這樣的情形,你老闆把大家都聚起來開了一個年初KPI目標制定會議,說到:「作為一個資深的技術團隊,app性能是我們技術團隊首抓的目標,其中很最要的一項就是app的崩潰率,去年我們app統計出來的崩潰率是千分之五,而我們的競爭對手的崩潰率只有萬分之五,相差了10倍!今年我們要趕超他們,最起碼也要和他們持平。」 你甚是贊同,但是你心裡卻又有點懷疑,對方的開發資源是我們的好幾倍而且個個都是資深老司機,我們團隊裡卻大多都是應屆生小鮮肉,這KPI能完成麼?

如果你遇到過以上的情況並且對此深表頭痛的話,那麼 大白健康系統--APP運行時Crash自動修復系統 將會是你的不二選擇!

APP運行時Crash自動修復+捕獲系統 的設計初衷,就是為了降低app的crash率。利用Objective-C語言的動態特性,採用AOP(Aspect Oriented Programming) 面向切面編程的設計思想,做到無痕植入。能夠自動在app運行時實時捕獲導致app崩潰的破環因子,然後通過特定的技術手段去化解這些破壞因子,使app免於崩潰,照樣可以繼續正常運行,為app的持續運轉保駕護航

Chapter 2 - 功能簡介

APP運行時Crash自動修復系統 的主要功能,可以用一句話來簡單的概括:對業務代碼的零侵入性地將原本會導致app崩潰的crash抓取住,消滅掉,保證app繼續正常地運行,再將crash的具體信息提取出來,實時返回給用戶

通過下面的一個小例子就可以很直觀的體現出來系統的作用:

調用以下的一段代碼

//test codeUIButton * testObj = [[UIButton alloc] init];[testObj performSelector:@selector(someMethod:)];

結果肯定會導致app的崩潰,因為testObj是一個UIButton對象,而UIButton並沒有實現 someMethod: 這個方法,所以向testObj發送someMethod:這個方法的時候,將會導致該方法無法在相關的方法列表裡找到,最終導致app的crash。

但是通過我們的crash防護系統,調用這段代碼時app並不會崩潰,同時XCode的Console如下: 可見對應的crash的信息(crash類型,原因,調用棧信息)均可以完整的列印在XCode的Console中。

說明我們的大白系統已經捕捉到了這個crash,將該crash消滅掉並且吐出來該crash的完整信息。

當然目前系統的功能並沒有強大到可以把所有的crash都處理掉,不過一些常見的高頻次發生的crash,系統均會針對他們一一處理。目前可以處理掉的crash類型具體有以下幾種:

unrecognized selector crash

KVO crash

NSNotification crash

NSTimer crash

Container crash(數組越界,插nil等)

NSString crash (字符串操作的crash)

Bad Access crash (野指針)

UI not on Main Thread Crash (非主線程刷UI(機制待改善))

對於每種類型的crash,安全系統都採取不同的方式,進行了對應的處理。 具體的處理細節詳見下章:Chapter 3 - 實現原理

Chapter 3 - 實現原理

前面已經提過,目前的安全防護系統可以覆蓋到8中類型的Crash,分別為:

unrecognized selector crash

KVO crash

NSNotification crash

NSTimer crash

Container crash(數組越界,插nil等)

NSString crash (字符串操作的crash)

Bad Access crash (野指針)

UI not on Main Thread Crash (非主線程刷UI (機制待改善))

接下來將一一詳細介紹這8種類型的Crash的防護的實現的具體原理:

3.1 Unrecognized Selector類型crash防護(Unrecognized Selector)3.1.1 unrecognized selector crash 產生原因

unrecognized selector類型的crash在app眾多的crash類型中佔著比較大的成分,通常是因為一個對象調用了一個不屬於它方法的方法導致的。 

例如調用以下一段代碼就會產生crash

//test codeUIButton * testObj = [[UIButton alloc] init];[testObj performSelector:@selector(someMethod:)];

具體crash時的表現見下圖:

要解決這中類型的crash,我們需要先了解清楚它產生的具體原因和流程。

3.1.2 方法調用流程

讓我們看一下方法調用在運行時的過程。

runtime中具體的方法調用流程大致如下:

1.首先,在相應操作的對象中的緩存方法列表中找調用的方法,如果找到,轉向相應實現並執行。

2.如果沒找到,在相應操作的對象中的方法列表中找調用的方法,如果找到,轉向相應實現執行

3.如果沒找到,去父類指針所指向的對象中執行1,2.

4.以此類推,如果一直到根類還沒找到,轉向攔截調用,走消息轉發機制。

5.如果沒有重寫攔截調用的方法,程序報錯。

3.1.3 攔截調用

在方法調用中說到了,如果沒有找到方法就會轉向攔截調用。

那麼什麼是攔截調用呢?

攔截調用就是,在找不到調用的方法程序崩潰之前,你有機會通過重寫NSObject的四個方法來處理:

+ (BOOL)resolveClassMethod:(SEL)sel;+ (BOOL)resolveInstanceMethod:(SEL)sel;//後兩個方法需要轉發到其他的類處理- (id)forwardingTargetForSelector:(SEL)aSelector;- (void)forwardInvocation:(NSInvocation *)anInvocation;

攔截調用的整個流程即Objective——C的消息轉發機制。其具體流程如下圖:

由上圖可見,在一個函數找不到時,runtime提供了三種方式去補救:

1、調用resolveInstanceMethod給個機會讓類添加這個實現這個函數

2、調用forwardingTargetForSelector讓別的對象去執行這個函數

3、調用forwardInvocation(函數執行器)靈活的將目標函數以其他形式執行。

如果都不中,調用doesNotRecognizeSelector拋出異常。

3.1.4 unrecognized selector crash 防護方案

既然可以補救,我們完全也可以利用消息轉發機制來做文章。那麼問題來了,在這三個步驟裡面,選擇哪一步去改造比較合適呢。

這裡我們選擇了第二步forwardingTargetForSelector來做文章。原因如下:

resolveInstanceMethod 需要在類的本身上動態添加它本身不存在的方法,這些方法對於該類本身來說冗餘的

forwardInvocation可以通過NSInvocation的形式將消息轉發給多個對象,但是其開銷較大,需要創建新的NSInvocation對象,並且forwardInvocation的函數經常被使用者調用,來做多層消息轉發選擇機制,不適合多次重寫

forwardingTargetForSelector可以將消息轉發給一個對象,開銷較小,並且被重寫的概率較低,適合重寫

選擇了forwardingTargetForSelector之後,可以將NSObject的該方法重寫,做以下幾步的處理:

1.動態創建一個樁類

2.動態為樁類添加對應的Selector,用一個通用的返回0的函數來實現該SEL的IMP

3.將消息直接轉發到這個樁類對象上。

流程圖如下:

注意如果對象的類本事如果重寫了forwardInvocation方法的話,就不應該對forwardingTargetForSelector進行重寫了,否則會影響到該類型的對象原本的消息轉發流程。

通過重寫NSObject的forwardingTargetForSelector方法,我們就可以將無法識別的方法進行攔截並且將消息轉發到安全的樁類對象中,從而可以使app繼續正常運行。

3.2 KVO類型crash防護(KVO)3.2.1 KVO crash 產生原因

KVO,即:Key-Value Observing,它提供一種機制,當指定的對象的屬性被修改後,則對象就會接受收到通知。簡單的說就是每次指定的被觀察的對象的屬性被修改後,KVO就會自動通知相應的觀察者了。

KVO機制在iOS的很多開發場景中都會被使用到。不過如果一不小心使用不當的話,會導致大量的crash問題。所以如果能找到一種方法能夠自動抓取這些由於開發者粗心所導致的KVO Crash問題的話,是有一定的價值的。

首先我們來看看通過會導致KVO Crash的兩種情形:

KVO的被觀察者dealloc時仍然註冊著KVO導致的crash,見下圖

添加KVO重複添加觀察者或重複移除觀察者(KVO註冊觀察者與移除觀察者不匹配)導致的crash,見下圖

3.2.2 KVO crash 防護方案

通常一個對象的KVO關係圖如下:

一個被觀察的對象(Observed Object)上有若干個觀察者(Observer),每個觀察者又觀察若干條KeyPath。

如果觀察者和keypath的數量一多,很容易理不清楚被觀察對象整個KVO關係,導致被觀察者在dealloc的時候,還殘存著一些關係沒有被註銷。 同時還會導致KVO註冊觀察者與移除觀察者不匹配的情況發生。

筆者曾經還遇到過在多線程的情況下,導致KVO重複添加觀察者或移除觀察者的情況。這類問題通常多數發生的比較隱蔽,不容易從代碼的層面去排查。

由上可見多數由於KVO而導致的crash原因是由於被觀察對象的KVO關係圖混亂導致。那麼如何來管理混亂的KVO關係呢。可以讓被觀察對象持有一個KVO的delegate,所有和KVO相關的操作均通過delegate來進行管理,delegate通過建立一張map來維護KVO整個關係。如下圖:

這樣做的好處有兩個:

1.如果出現KVO重複添加觀察者或重複移除觀察者(KVO註冊觀察者與移除觀察者不匹配)的情況,delegate可以直接阻止這些非正常的操作。

2.被觀察對象dealloc之前,可以通過delegate自動將與自己有關的KVO關係都註銷掉,避免了KVO的被觀察者dealloc時仍然註冊著KVO導致的crash。

被swizzle的方法分別是:

- (void)addObserver:(NSObject *)observer            forKeyPath:(NSString *)keyPath                options:(NSKeyValueObservingOptions)options                context:(nullable void *)context;- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;

關於

- (void)addObserver:(NSObject *)observer         forKeyPath:(NSString *)keyPath            options:(NSKeyValueObservingOptions)options            context:(void *)context

方法改造流程如下圖:

通過上面的流程,將observerd對象的所有kvo相關的observer信息全部轉移到KVOdelegate上,並且避免了相同kvoinfo被重複添加多次的可能性。

關於

- (void)removeObserver:(NSObject *)observer            forKeyPath:(NSString *)keyPath               context:(void *)context

方法改造流程如下圖:

移除一個keypath的Observer時,當delegate的kvoInfoMap中找不到key為該keypath的時候,說明此時delegate並沒有持有對應keypath的observer,即說明移除了一個不匹配的觀察者,此時如果再繼續操作會導致app崩潰,所以應該及時中斷流程,然後統計異常信息。

當keypath對應的KVOInfo列表(infoArray)為空的時候,說明此時delegate已經不再持有任何和keypath相關的observer了。這時應該調用原有removeObserver的方法將delegate對應的觀察者移除。

注意到在檢查遍歷infoArray的時侯,除了要刪除對應的info信息,還多了一步檢查info.observer == nil的過程,是因為如果observer為nil,那麼此時如果keypath對應的值變化的話,也會因為找不到observer而崩潰,所以需要做這一步來阻止該種情況的發生。

關於

- (void)observeValueForKeyPath:(NSString *)keyPath                      ofObject:(id)object                        change:(NSDictionary<NSString *,id> *)change                       context:(void *)context

方法改造流程如下圖:

delegate對於observeValueForKeyPath方法的修改最主要的地法規,在於將對應的響應方法轉移給真正的KVO Observer,通過keyInfoMap找到keypath對應的KVOInfo裡面預先存儲好的observer,然後調用observer原本的響應方法

同時在遍歷InfoArray的時候,發現info.observerw == nil的時候,需要及時將其清除掉,避免KVO的觀察者observer被釋放後value變化導致的crash

最後,針對 KVO的被觀察者dealloc時仍然註冊著KVO導致的crash 的情況

可以將NSObject的dealloc swizzle, 在object dealloc的時候自動將其對應的kvodelegate所有和kvo相關的數據清空,然後將kvodelegate也置空。避免出現KVO的被觀察者dealloc時仍然註冊著KVO而產生的crash

3.3 NSNotification類型crash防護(NSNotification)3.3.1 NSNotification crash 產生原因

當一個對象添加了notification之後,如果dealloc的時候,仍然持有notification,就會出現NSNotification類型的crash。 

NSNotification類型的crash多產生於程式設計師寫代碼時候犯疏忽,在NSNotificationCenter添加一個對象為observer之後,忘記了在對象dealloc的時候移除它。

所幸的是,蘋果在iOS9之後專門針對於這種情況做了處理,所以在iOS9之後,即使開發者沒有移除observer,Notification crash也不會再產生了。

不過針對於iOS9之前的用戶,我們還是有必要做一下NSNotification Crash的防護的。

3.3.2 NSNotification crash 防護方案

NSNotification Crash的防護原理很簡單, 利用method swizzling hook NSObject的dealloc函數,再對象真正dealloc之前先調用一下
[[NSNotificationCenter defaultCenter] removeObserver:self] 即可。

注意到並不是所有的對象都需要做以上的操作,如果一個對象從來沒有被NSNotificationCenter 添加為observer的話,在其dealloc之前調用removeObserver完全是多此一舉。 所以我們hook了NSNotificationCenter的 addObserver:(id)observer selector:(SEL)aSelector name:(NSString *)aName object:(id)anObject

函數,在其添加observer的時候,對observer動態添加標記flag。這樣在observer dealloc的時候,就可以通過flag標記來判斷其是否有必要調用removeObserver函數了。

3.4 NSTimer類型crash防護(NSTimer)3.4.1 NSTimer crash 產生原因

在程序開發過程中,大家會經常使用定時任務,但使用NSTimer的 scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: 接口做重複性的定時任務時存在一個問題:NSTimer會 強引用 target實例,所以需要在合適的時機invalidate 定時器,否則就會由於定時器timer強引用target的關係導致 target不能被釋放,造成內存洩露,甚至在定時任務觸發時導致crash。 crash的展現形式和具體的target執行的selector有關。

與此同時,如果NSTimer是無限重複的執行一個任務的話,也有可能導致target的selector一直被重複調用且處於無效狀態,對app的CPU,內存等性能方面均是沒有必要的浪費。

所以,很有必要設計出一種方案,可以有效的防護NSTimer的濫用問題。

3.4.2 NSTimer crash 防護方案

上面的分析可見,NSTimer所產生的問題的主要原因是因為其沒有再一個合適的時機invalidate,同時還有NSTimer對target的強引用導致的內存洩漏問題。

那麼解決NSTimer的問題的關鍵點在於以下兩點:

NSTimer對其target是否可以不強引用

是否找到一個合適的時機,在確定NSTimer已經失效的情況下,讓NSTimer自動invalidate

關於第一個問題,target的強引用問題。 可以用如下圖的方案來解決:

在NSTimer和target之間加入一層stubTarget,stubTarget主要做為一個橋接層,負責NSTimer和target之間的通信。

同時NSTimer強引用stubTarget,而stubTarget弱引用target,這樣target和NSTimer之間的關係也就是弱引用了,意味著target可以自由的釋放,從而解決了循環引用的問題。

上文提到了stubTarget負責NSTimer和target的通信,其具體的實現過程又細分為兩大步:

step 1. swizzle NSTimer中scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: 相關的方法,在新方法中動態創建stubTarget對象,stubTarget對象弱引用持有原有的target,selector,timer,targetClass等properties。然後將原target分發stubTarget上,selector回調函數為stubTarget的fireProxyTimer:,流程如下圖:

step 2. 通過stubTarget的fireProxyTimer:來具體處理回調函數selector的處理和分發,流程如下圖:

因為stubTarget的介入,原有的target已經可以不受NSTimer強引用的牽制,而自由的釋放。

由上圖流程可知,當NSTimer的回調函數fireProxyTimer:被執行的時候,會自動判斷原target是否已經被釋放,如果釋放了,意味著NSTimer已經無效,此時如果還繼續調用原有target的selector很有可能會導致crash,而且是沒有必要的。所以此時需要將NSTimer invalidate,然後統計上報錯誤數據。如此一來就做到了NSTimer在合適的時機自動invalidate。

3.5 Container類型crash防護(Container)3.5.1 Container crash 產生原因

Container 類型的crash 指的是容器類的crash,常見的有NSArray/NSMutableArray/NSDictionary/NSMutableDictionary/NSCache的crash。 一些常見的越界,插入nil,等錯誤操作均會導致此類crash發生。 由於產生的原因比較簡單,就不展開來描述了。

該類crash雖然比較容易排查,但是其在app crash概率總比還是挺高,所以有必要對其進行防護。

3.5.2 Container crash 防護方案

Container crash 類型的防護方案也比較簡單,針對於NSArray/NSMutableArray/NSDictionary/NSMutableDictionary/NSCache的一些常用的會導致崩潰的API進行method swizzling,然後在swizzle的新方法中加入一些條件限制和判斷,從而讓這些API變的安全,這裡就不展開來具體描述了。

3.6 NSString類型crash防護(NSString)

NSString/NSMutableString 類型的crash的產生原因和防護方案與Container crash很相像,這裡也不展開來描述了。

3.7 野指針類型crash防護(Bad Access)3.7.1 野指針crash 產生原因

在App的所有Crash中,訪問野指針導致的Crash佔了很大一部分,野指針類型crash的表現為:Exception Type:SIGSEGV,Exception Codes: SEGV_ACCERR 或者 如下圖:

解決野指針導致的crash往往是一件棘手的事情,一來產生crash 的場景不好復現,二來crash之後console的信息提供的幫助有限。 XCode本身為了便於開放調試時發現野指針問題,提供了Zombie機制,能夠在發生野指針時提示出現野指針的類,從而解決了開發階段出現野指針的問題。然而針對於線上產生的野指針問題,依舊沒有一個比較好的辦法來定位問題。

所以,因為野指針出現概率高而且難定位問題,非常有必要針對於野指針專門做一層防護措施。

3.7.2 野指針crash 防護方案

野指針問題的解決思路方向其實很容易確定,XCode提供了Zombie的機制來排查野指針的問題,那麼我們這邊可以實現一個類似於Zombie的機制,加上對zombie實例的全部方法攔截機制 和 消息轉發機制,那麼就可以做到在野指針訪問時不Crash而只是crash時相關的信息。 

同時還需要注意一點:因為zombie的機制需要在對象釋放時保留其指針和相關內存佔用,隨著app的進行,越來越多的對象被創建和釋放,這會導致內存佔用越來越大,這樣顯然對於一個正常運行的app的性能有影響。所以需要一個合適的zombie對象釋放機制,確定zombie機制對內存的影響是有限度的。

improve版的zombie機制的實現主要分為以下四個環節:

step 1. method swizzling替換NSObject的allocWithZone方法,在新的方法中判斷該類型對象是否需要加入野指針防護,如果需要,則通過objc_setAssociatedObject為該對象設置flag標記,被標記的對象後續會進入zombie流程

流程圖如下:

做flag標記是因為很多系統類,比如NSString,UIView等創建,釋放非常頻繁,而這些實例發生野指針概率非常低。基本都是我們自己寫的類才會有野指針的相關問題,所以通過在創建時 設置一個標記用來過濾不必要做野指針防護的實例,提高方案的效率。

同時做判斷是否要加入標記的條件裡面,我們加入了黑名單機制,是因為一些特定的類是不適用於添加到zombie機制的,會發生崩潰(例如:NSBundle),而且所以和zombie機制相關的類也不能加入標記,否則會在釋放過程中循環引用和調用,導致內存洩漏甚至棧溢出。

step 2. method swizzling替換NSObject的dealloc方法,對flag標記的對象實例調用objc_destructInstance,釋放該實例引用的相關屬性,然後將實例的isa修改為HTZombieObject。通過objc_setAssociatedObject 保存將原始類名保存在該實例中。

流程圖如下:

調用objc_destructInstance的原因:

這裡參考了系統在Object-C Runtime 中NSZombies實現,dealloc最後會調到objectdispose函數,在這個函數裡面 其實也做了三件事情,

1)調用objc_destructInstance釋放該實例引用的相關實例

2)將該實例的isa修改為stubClass,接受任意方法調用

3)釋放該內存

官方文檔對objc_destructInstance的解釋為:

Destroys an instance of a class without freeing memory and removes any associated references this instance might have had.

說明objc_destructInstance會釋放與實例相關聯的引用,但是並不釋放該實例等內存。

step 3. 在HTZombieObject 通過消息轉發機制forwardingTargetForSelector處理所有攔截的方法,根據selector動態添加能夠處理方法的響應者HTStubObject 實例,然後通過 objc_getAssociatedObject 獲取之前保存該實例對應的原始類名,統計錯誤數據。

流程圖如下:

HTZombieObject的處理和unrecognized selector crash的處理是一樣,主要的目的就是攔截所有傳給HTZombieObject的函數,用一個返回為空的函數來替換,從而達到程序不崩潰的目的。

step 4. 當退到後臺或者達到未釋放實例的上限時,則在ht_freeSomeMemory方法中調用原有dealloc方法釋放所有被zombie化的實例

綜上所述,可以用下圖總結一下bad access類型crash的防護流程:

3.7.3 相關的風險

1.做了野指針防護,通過動態插入一個空實現的方法來防止出現Crash,但是業務層面的表現難以確定,可能會進入業務異常的狀態。需要擬定一下如何展現該問題給用戶的方案

2.由於做了延時釋放若干實例,對系統總內存會產生一定影響,目前將內存的緩衝區開到2M左右,所以應該沒有很大的影響,但還是可能潛在一些風險。

3.延時釋放實例是根據相關功能代碼會聚焦在某一個時間段調用的假設前提下,所以野指針的zombie保護機制只能在其實例對象仍然緩存在zombie的緩存機制時才有效,若在實例真正釋放之後,再調用野指針還是會出現Crash。

3.8 非主線程刷UI類型crash防護(UI not on Main Thread)

在非主線程刷UI將會導致app運行crash,有必要對其進行處理。

目前初步的處理方案是swizzle UIView類的以下三個方法:

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

在這三個方法調用的時候判斷一下當前的線程,如果不是主線程的話,直接利用 dispatch_async(dispatch_get_main_queue(), ^{ //調用原本方法 });

來將對應的刷UI的操作轉移到主線程上,同時統計錯誤信息。

但是真正實施了之後,發現這三個方法並不能完全覆蓋UIView相關的所有刷UI到操作,但是如果要將全部到UIView的刷UI的方法統計起來並且swizzle,感覺略笨拙而且不高效。 

所以作者依舊在尋找,看是否有更好的方案來解決該問題。

Chapter 4 - 使用手冊

目前sdk實現了以下的功能和配置:

1. 配置需要防護的crash類型

可以根據自身需要,選擇一定的crash防護配置,通過以下的接口進行配置:

- (void)configSafetyGuardService:(HTSafetyGuardType)SafetyGuardType;

其中可以配置的SafetyGuardType有:

HTSafetyGuardType_None

HTSafetyGuardType_All

HTSafetyGuardType_UnrecognizedSelector

HTSafetyGuardType_KVO

HTSafetyGuardType_BadAccess

HTSafetyGuardType_Notification

HTSafetyGuardType_Timer

HTSafetyGuardType_Container

HTSafetyGuardType_String

HTSafetyGuardType_UI

可以根據自己項目的需求自行選擇需要防護的類型。

2. 實時 開啟/暫停 安全防護功能

配置完畢之後,需要調用- (void)start;來開啟防護,防護的開關是實時的(無需重啟app),可以在任意的時刻選擇 開啟/關閉 防護功能。 

通過 - (BOOL)isWorking 接口可以獲取當前防護功能的狀態。

通過 - (void)start 接口實時開啟防護功能

通過 - (void)stop 接口實時關閉防護功能

3. 配置白名單和黑名單,指定對應的想 加上/去掉 安全防護功能的類和對象

由於不同類實現的特殊性,考慮到可能某些類並不需要開啟防護功能。 所以提供了黑名單的功能。 在黑名單裡面的類本身以及其子類,都不會進入防護的範圍。

白名單的出現是因為作者在開發的時候發現一些系統自帶的類是沒有必要進入防護範圍的,所以將整體防護的範圍調整到所有用戶自定義的類裡面。 但是之後又發現絕大多數的crash和一些常用的系統的類(例如NSString,NSDictionary,UIView等等)有很強的聯繫,針對於這些常用的系統類還是很有必要開啟防護的。所以針對這些需要防護的系統類,專門提供了白名單的功能。

注意:野指針類型的防護,由於其特殊性,不適用於這套白名單和黑名單。 其自身會維護一套新的黑白名單,詳見:3.7 野指針類型Crash防護

4. 設置異常處理handler,指定出現crash被抓取情況之後,用戶想自定義的操作

出現了crash,並且被我們的系統捕捉到加以處理之後,用戶可能還需要進一步的處理,例如上傳埋點等。這時可以通過設置一個handler來實現, HTExceptionHandler會將crash的信息通過HTCrashInfo的形式來返回。

HTCrashInfo內包含了:

以上接口具體詳細的信息均可以在(HTSafetyGuardService.h)中找到。(注意HTSafetyGuardService是單例)

由於目前sdk還未經過完整的功能測試和性能測試,故暫不開放對應的sdk。等作者覺得項目質量達到了一定的標準之後,會將項目sdk開放出來。如果對該項目感興趣,可以聯繫 taozeyu890217@126.com ,歡迎一起研究。

(更多技術分享,長按下方二維碼關注網易前端技術部公眾號)

相關焦點

  • 再談 iOS App Crash 防護
    去年,網易杭州研究院曾經針對 crash 的防護有提出『大白健康系統--iOS APP 運行時 Crash 自動修復系統[1]』方案,使得 crash 防護這個想法真正被落實,但至今該方案的具體實現並沒有被開源。圈子裡也有一些開發朋友,基於這套方案設計並開源了自己的 「Baymax」,比如『老司機 iOS 周報第七期[2]』中曾提到的 BayMaxProtector[3]。
  • iOS App 連續閃退時如何上報 crash 日誌
    crash 日誌上報有兩個難點:這篇文章介紹下出現第二種情況時,如何準確上報 crash 日誌。首先我們需要一種比較可靠的方式,可以在 app 啟動時判斷上次是否發生了啟動 crash。介紹一個可行的思路。如何檢測連續閃退連續閃退包含兩個元素,閃退和連續。只有這兩個元素同時具備時,才會影響我們的日誌上傳。
  • 如何更好地利用 iOS Crash Log
    但除了內部測試,大多數情況我們並不能拿到發生 crash 的設備。蘋果為 TestFlight 和 App Store 上的 app 提供了 crash log 收集功能。該功能只有在用戶每次更新或激活 iOS / macOS 系統後主動選擇同意 Apple 分享 App 使用數據和 crash 信息給開發者的選項後才能工作。
  • APP測試流程
    5) app切換到後臺,再切回前臺的校驗 6) 切換到後臺,再切換回前臺的測試 7) 密碼更換後,檢查有數據交換時是否進行了有效身份的校驗 8) 支持自動登錄的應用在進行數據交換時,檢查系統是否能自動登錄成功並且數據操作無誤。
  • Win10系統自動修復時出現無限死循環或提示無法修復怎麼辦 如何禁用自動修復服務
    Win10是個完整的系統,不僅自帶各種應用,如計算器、畫圖等,還有系統重置、自動修復等功能,在系統癱瘓時使用系統自帶的工具來修復、還原。
  • iOS Memory 內存詳解:從作業系統內存管理、iOS 系統內存到app 內存管理
    在此基礎之上,本文會進一步在 iOS 系統層面進行分析,包括 iOS 整體的內存機制,以及 iOS 系統運行時的內存佔用的情況。最後會將粒度縮小到 iOS 中的單個 app,講到單個 app 的內存管理策略。1. 作業系統的內存機制為了從根本上更好地理解和分析 iOS 系統上的內存特性,我們首先需要正確理解一般作業系統通用的內存機制。
  • 給iOS 模擬器「安裝」app文件
    head其實是找出最近一次我們運行模擬器的app的路徑。為了保證我們打包是正確的,建議先運行一下我們要打包的app,一般我們Scheme裡面的Run都是debug product(如果這裡有更改,那就改成對應debug的Scheme),確保是我們要給設計師審核的app,之後再運行這個ditto命令。
  • MySQL 資料庫崩潰(crash)的常見原因和解決辦法
    檢查 MySQL 資料庫的啟動時間Linux 系統中的 systemd 會在 mysqld 進程 crash 後自動重新啟動 MySQL 的服務,需要注意的是使用 kill -9 殺死 mysqld 進程系統會自動重新啟動,而只使用 kill 命令則不會重新啟動,因為執行 kill 命令,系統會發送一個 SIGTERM 信號給 mysqld,mysql 資料庫會正常關閉,日誌中會出現類似下面的記錄
  • 部分用戶更新系統後提示「自動修復」,無法進入系統
    部分用戶更新系統後提示「自動修復」,無法進入系統,如下圖所示: 圖1 操作步驟
  • win10系統提示:正在準備自動修復
    出現這樣狀況說明系統文件遭到了破壞,導致系統不完整,所以開機系統就會自動嘗試修復,而無法修復就說明系統文件遭到了不可逆的損壞。
  • Flutter從配置安裝到運行第一個app詳解
    Flutter 是谷歌今年年初提出的一個新工具,可以跨平臺開發安卓,ios,還支持kotlin語法,最近研究了一下Flutter,這個工具從安裝到運行經歷了九九八十一難,好在終於成功運行起來了。這個和配置java環境變量是同樣的做法,找到flutter安裝路徑,比如我的是E:\develop\flutter\bin,我複製這個路徑,右鍵我的電腦-->屬性-->高級系統屬性-->環境變量-->找到PATH,點擊編輯-->在最後把剛複製的路徑粘貼進來,-->一直保存確定就可以了。
  • 一鍵自動檢測和修復Windows系統+CMD命令行一行命令獲取公網ip
    一鍵自動檢測和修復Windows系統+CMD命令行一行命令獲取公網ip▲一鍵自動檢測和修復Windows系統▲在我們日常使用電腦時,有時候總能遇到系統卡頓、崩潰、未響應之類的問題。很多人在遇到問題時都會選擇直接重裝系統來解決,其實沒必要。今天就來教大家兩個比較實用的自動檢測和修復指令。
  • 我的iPhone6手機系統該不該升級到ios13?網友評論醉了
    版本大更新就是ios12升級到ios13,修復的bug和內容會很多。網上都說差了兩個版本盡不要升級。比如手機現在是ios11,建議不要直接升到ios13。比如說我的手機,給條指令,你的手機型號是iPhone6,系統是12.3,最新版是13。大家都說蘋果升級會變慢,是讓我們換手機,你認為呢?其實手機升級變慢,多少還是有一點依據的。手機一代又一代,ios總是在更新,也大多在修復最近版本的Bug。
  • 電腦開機顯示自動修復失敗無法進入系統,解決方法
    開機進不了系統,藍屏顯示自動修復失敗,立馬選了啟動修復,但可想而知不起作用1)是不是之前沒有正常將系統關閉?
  • APP測試之Monkey壓力測試(二)
    (這些按鍵通常被保留,由系統使用,如Home、Back、Start Call、End Call及音量控制鍵);[--pct-appswitch PERCENT] -6.調整啟動Acticity百分比(在隨機間隔裡,Monkey將執行一個startActivity()調用,作為最大程度覆蓋包中全部Activity的一種方法,從一個Activity跳轉到另一個Activity);
  • ios crash的原因與抓取crash日誌的方法
    crash的產生來源於兩種問題:違反iOS策略被幹掉,以及自身的代碼bug。1.IOS策略1.1 低內存閃退前面提到大多數crash日誌都包含著執行線程的棧調用信息,但是低內存閃退日誌除外,這裡就先看看低內存閃退日誌是什麼樣的。
  • 完美運行安卓APP?Win11安卓子系統深度體驗評測
    嚇得小編直接從床上爬了起來連夜進行測試,到底wsa(安卓子系統)能否完美運行安卓APP呢?下面就讓小夜帶大家看上一看吧!先公布答案:符合預期,但是任重而道遠。亮點1:底層技術+交互Win11的安卓子系統需要電腦開啟VT(虛擬技術),這一點和安卓模擬器是一樣的,但是還需要開啟Hyper-V。
  • Win10為啥總是自動修復?【解決】
    win10系統在啟動的時候,如果系統多次非正常關機或啟動失敗後會自動啟動修復。
  • 首個蘋果IOS模擬器,電腦運行IOS系統,這可不是假哦!!
    pc端運行一個安卓模擬器我們見得太多了,因為這個不稀奇,因為太多太多主播都是這樣做的,而ios模擬器在很長一段時間市場上都是空白的,這次神哥給大家分享一款蘋果的模擬器
  • 如何排除WIN7系統的啟動修復故障
    最近,不少朋友說他們的 Win7作業系統,在啟動的時候,出現自動修復且無法修復的故障,那麼,這是怎麼一回事呢?我們又該怎麼辦呢?