最近正好有空恩够,總結(jié)一下以前項(xiàng)目中遇到的幾種常見崩潰,并且在無侵入的情況下解決這些崩潰栖秕。
常見的崩潰類型
1.數(shù)組越界,nil值初始化導(dǎo)致的崩潰缓熟。
2.對(duì)字典插入nil值累魔,或者讀取NSNULL導(dǎo)致的崩潰摔笤。
3.字符串的截取越界導(dǎo)致的崩潰。
4.doesNotRecognizeSelector導(dǎo)致的崩潰垦写。
5.子線程初始化UIView導(dǎo)致的崩潰吕世。
6.KVO的重復(fù)添加、刪除梯投,或者忘了刪除導(dǎo)致的崩潰命辖。
對(duì)于以上崩潰,如果是新產(chǎn)品分蓖,或者是代碼全部是可以修改的情況下尔艇,可以通過檢查代碼的方式來解決這些問題,但是對(duì)于一些老的項(xiàng)目么鹤,或者集成了第三方庫的項(xiàng)目呢终娃,老項(xiàng)目修改動(dòng)作太大,第三方庫只能指望更新來解決了蒸甜,這都不是我們需要的方式棠耕,所以這種情況下,就需要我們的無侵入解決方案了柠新。
說到無侵入窍荧,大家首先想到的肯定是Method Swizzling,沒錯(cuò)恨憎,下面我們就利用Method Swizzling來解決以上崩潰蕊退。
先寫上大家熟悉的方法交換的代碼
+ (void)exchangeInstanceMethod:(Class)anClass originMethodSel:(SEL)originSEL replaceMethodSel:(SEL)replaceSEL{
MethodorigIndex =class_getInstanceMethod(anClass, originSEL);
MethodoverrideIndex =class_getInstanceMethod(anClass, replaceSEL);
if(!origIndex || !overrideIndex) {
return;
}
method_exchangeImplementations(origIndex, overrideIndex);
}
+ (void)exchangeClassMethod:(Class)anClass originMethodSel:(SEL)originSEL replaceMethodSel:(SEL)replaceSEL
{
MethodorigIndex =class_getClassMethod(anClass, originSEL);
MethodoverrideIndex =class_getClassMethod(anClass, replaceSEL);
if(!origIndex || !overrideIndex) {
return;
}
method_exchangeImplementations(origIndex, overrideIndex);
}
然后說一下整體的思路
1.數(shù)組越界,nil值初始化導(dǎo)致的崩潰憔恳。
這種情況要解決很容易瓤荔,網(wǎng)上很多這方面的文章,就是通過方法交換原NSArray的objectAtIndex喇嘱,然后加一層索引判斷就夠了茉贡,這里就不多做介紹了。
需要特別注意的就是NSArray初始化的時(shí)候有空值的情況者铜。
2.對(duì)字典插入nil值腔丧,或者讀取NSNULL導(dǎo)致的崩潰。
這種崩潰的解決思路和NSArray一樣作烟,單獨(dú)提出來愉粤,只是因?yàn)榻?jīng)常出現(xiàn)服務(wù)器返回的數(shù)據(jù)是null的远舅,如果在使用時(shí)不對(duì)數(shù)據(jù)類型進(jìn)行判斷的話铃辖,就會(huì)出現(xiàn)NSNULL類型與所需要的類型不一樣,導(dǎo)致崩潰 择镇。所以這是一個(gè)需要注意的地方。
3.字符串的截取越界導(dǎo)致的崩潰影暴。
崩潰處理方式與上面一樣错邦。
說到這,再提個(gè)概念類簇型宙,只有明白什么是類簇撬呢,才能知道為什么我們做方法交換的時(shí)候不直接使用[self class],我們上面要修改的幾個(gè)類NSArray妆兑,NSDictionary魂拦,NSString都是類簇,它們的Class比較多搁嗓,需要盡量多的枚舉所有可能的Class芯勘。
4.doesNotRecognizeSelector導(dǎo)致的崩潰
doesNotRecognizeSelector也是一種比較常見的崩潰,相信大家都了解iOS的消息轉(zhuǎn)發(fā)機(jī)制的幾個(gè)步驟了腺逛,我們就不再重復(fù)說明了荷愕,接下來我們?cè)賮砜纯慈绾芜x擇我們的實(shí)現(xiàn)。
1)動(dòng)態(tài)決議
需要?jiǎng)討B(tài)實(shí)現(xiàn)這個(gè)未知的方法屉来,而且需要考慮到參數(shù)問題路翻,比較麻煩,不采用茄靠。
2)備用接收
將這個(gè)未知的方法轉(zhuǎn)交給其它對(duì)象,結(jié)果還是需要實(shí)現(xiàn)這個(gè)未知方法蝶桶,同上慨绳,不采用。
3)消息轉(zhuǎn)發(fā)
完整的消息轉(zhuǎn)發(fā)真竖,將未知的方法打包成一個(gè)NSInvocation轉(zhuǎn)交給別的對(duì)象脐雪,但我們?cè)趂orwardInvocation:完全可以不實(shí)現(xiàn)任何真的轉(zhuǎn)發(fā),就可以攔截掉這次的轉(zhuǎn)發(fā)恢共,所以采用這種方式最合適战秋。
在實(shí)現(xiàn)的時(shí)候,我們?cè)贛ethodSignature方法里讨韭,將方法簽名指向一個(gè)我們自定義的類的方法脂信,并且拿到簽名,返回給系統(tǒng)透硝。在forwardInvocation里狰闪,不做任何實(shí)現(xiàn)就可以了。
5.子線程初始化UIView導(dǎo)致的崩潰濒生。
這種情況也比較簡(jiǎn)單埋泵,就是把UIView的初始化方法及addSubview的方法交換一下,然后判斷一下當(dāng)前線程是不是主線程,如果不是主線程丽声,那么GCD到主線程里實(shí)現(xiàn)就行了礁蔗。
6.KVO的重復(fù)添加、刪除雁社,或者忘了刪除導(dǎo)致的崩潰浴井。
KVO出現(xiàn)最多的崩潰可能就是忘記刪除或者重復(fù)刪除了,要解決這個(gè)問題歧胁,最簡(jiǎn)單的實(shí)現(xiàn)就是記錄每次添加的observer和keyPath滋饲,所以我在addObserver:forKeyPath:options:context:這個(gè)方法里新建了一個(gè)字典,用來記錄observer和keyPath喊巍。
下面需要解決的就是什么時(shí)候去調(diào)用的問題屠缭,我首先想到的是在dealloc時(shí)去判斷是否添加了KVO的監(jiān)視,但是當(dāng)我直接交換了dealloc方法后發(fā)現(xiàn)崭参,這個(gè)方法調(diào)用的太多了呵曹,并不適合直接交換,不然整個(gè)程序都會(huì)卡頓起來何暮,需要找一個(gè)時(shí)機(jī)奄喂,于是我又修改為在添加監(jiān)視的時(shí)候去交換dealloc,這次成功了海洼,這樣可以減少對(duì)不必要的類進(jìn)行方法交換跨新,同時(shí)提高效率。
唯一需要注意的是在ARC的情況下坏逢,不能直接@selector(dealloc)來做方法交換域帐,需要變形一下NSSelectorFromString(@"dealloc"),這樣才能做方法交換是整。