成因
野指針就是指向一個(gè)已刪除的對(duì)象或者受限內(nèi)存區(qū)域的指針呻粹。
我們寫(xiě)C++的時(shí)候強(qiáng)調(diào)指針初始化為NULL壕曼,強(qiáng)調(diào)用完后也為其賦值為NULL,誰(shuí)分配的誰(shuí)回收等浊,來(lái)避免野指針的問(wèn)題腮郊。
比較常見(jiàn)的就是這個(gè)指針指向的內(nèi)存,在別處被回收了筹燕,但是這個(gè)指針不知道轧飞,依然還指向這塊內(nèi)存。
MRC 時(shí)代因?yàn)橐糜?jì)數(shù)手動(dòng)控制撒踪,所以?xún)?nèi)存很容易在別處被回收过咬。ARC解決了大部分這種問(wèn)題。制妄、
在iOS9之前掸绞,系統(tǒng)庫(kù)的delegate
和target-action
有一部分是assign(unsafe_unretain)
的形式,這時(shí)候如果內(nèi)存在別處被回收了耕捞,也是會(huì)出現(xiàn)野指針的衔掸。
所以iOS9之后這些地方就改成了weak內(nèi)存修飾符烫幕,內(nèi)存被回收的時(shí)候通過(guò)weak表,把這些指針設(shè)為nil敞映。也大幅度減少了野指針的出現(xiàn)较曼。
如果現(xiàn)在在工程中依然頻繁出現(xiàn)野指針,幾乎可以肯定是錯(cuò)誤地使用了內(nèi)存振愿。
表現(xiàn):Crash
對(duì)于Mach
捷犹、Unix
、NSException
三種不同層級(jí)的crash,NSException比較好說(shuō)埃疫,可以直接定位到OC代碼伏恐。問(wèn)題主要來(lái)自EXC_BAD_ACCESS(SIGSEGV)
這種異常,難以在我們的應(yīng)用代碼中定位栓霜。
- SIGILL 執(zhí)行了非法指令翠桦,一般是可執(zhí)行文件出現(xiàn)了錯(cuò)誤
- SIGTRAP 斷點(diǎn)指令或者其他trap指令產(chǎn)生
- SIGABRT 調(diào)用abort產(chǎn)生
- SIGBUS 非法地址。比如錯(cuò)誤的內(nèi)存類(lèi)型訪(fǎng)問(wèn)胳蛮、內(nèi)存地址對(duì)齊等
- SIGSEGV 非法地址销凑。訪(fǎng)問(wèn)未分配內(nèi)存、寫(xiě)入沒(méi)有寫(xiě)權(quán)限的內(nèi)存等
- SIGFPE 致命的算術(shù)運(yùn)算仅炊。比如數(shù)值溢出斗幼、NaN數(shù)值等
實(shí)際我們遇到Mach Exception
絕大部分都是野指針的問(wèn)題。SIGSEGV/SIGABRT/SIGTRAP 比較多見(jiàn)抚垄。
野指針問(wèn)題表現(xiàn)千奇百怪蜕窿,而且因?yàn)楸罎⒌牡胤讲⒉皇窃斐梢爸羔樀牡胤剑译y以重現(xiàn)呆馁,所以問(wèn)題往往難以定位桐经。
騰訊Bugly的這張圖可以看到,野指針幾乎可以造成各種類(lèi)型的
Mach Exception
浙滤。
定位工具
Zoombie Object
這是目前幫助最大的調(diào)試模式阴挣。實(shí)現(xiàn)原理就是 hook 住了對(duì)象的dealloc方法,通過(guò)調(diào)用自己的__dealloc_zombie
方法來(lái)把對(duì)象進(jìn)行僵尸化纺腊。
id object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
正常的對(duì)象釋放方法如上畔咧,但是僵尸對(duì)象調(diào)用了objc_destructInstance
后就直接return了,不再free(obj);
揖膜。同時(shí)生成一個(gè)"_NSZombie_" + clsName
類(lèi)名誓沸,調(diào)用objc_setClass(self, zombieCls);
修改對(duì)象的 isa 指針,令其指向特殊的僵尸類(lèi)壹粟。
如果這個(gè)對(duì)象再次收到消息,objc_msgsend
的時(shí)候拜隧,調(diào)用abort()崩潰并打印出調(diào)用的方法。
野指針指向的內(nèi)存沒(méi)有被覆蓋的時(shí)候,或者被覆蓋成可以訪(fǎng)問(wèn)的內(nèi)存的時(shí)候虹蓄,不一定會(huì)出現(xiàn)崩潰犀呼。這個(gè)時(shí)候向?qū)ο蟀l(fā)送消息,不一定會(huì)崩潰(可能剛好有這個(gè)方法)薇组,或者向已經(jīng)釋放的對(duì)象發(fā)送消息外臂。 但是如果野指針指向的是僵尸對(duì)象,那就一定會(huì)崩潰了律胀,會(huì)崩潰在僵尸對(duì)象第一次被其它消息訪(fǎng)問(wèn)的時(shí)候宋光。
Zombie Object without Xcode
僵尸對(duì)象必須在連接X(jué)code中debug的時(shí)候使用,如果我們想跟我們的崩潰收集工具集成在一起炭菌,就需要自己實(shí)現(xiàn)類(lèi)似Zombie Object的東西罪佳。
邏輯是通過(guò)hook住NSObject的根類(lèi)的dealloc方法,然后在新的dealloc方法中將本來(lái)即將釋放的對(duì)象的isa指針改為指向我們創(chuàng)建的一個(gè)新的僵尸類(lèi)黑低。
iOS使用代碼排查野指針錯(cuò)誤
和 開(kāi)發(fā)自己的NSZombie這兩篇文章里介紹了在代碼里實(shí)現(xiàn)類(lèi)似Zoombie Object的方法赘艳,然而實(shí)際上是無(wú)法使用的,這兩種實(shí)現(xiàn)跟 Zombie Object 實(shí)現(xiàn)上不小的區(qū)別克握,實(shí)際應(yīng)用中有大量誤判的情況蕾管。
誤判的原因主要是dealloc的實(shí)現(xiàn)和僵尸類(lèi)的實(shí)現(xiàn)跟Zombie Object不一樣。
參考Apple的源碼菩暗,可以看到Apple是完全調(diào)用了objc_destructInstance
函數(shù)的掰曾。而其它人的實(shí)現(xiàn)要么沒(méi)有調(diào)用這個(gè)函數(shù),要么只做了一部分停团。對(duì)于一個(gè)OC對(duì)象的dealloc來(lái)說(shuō)旷坦,主要包括兩部分,一部分是objc_destructInstance
,一部分是free(self)
佑稠。objc_destructInstance
里包括了移除弱引用秒梅,移除關(guān)聯(lián)對(duì)象,c++析構(gòu)等等讶坯。這些邏輯不能省略番电。
- (void)dealloc
{
const char *className = object_getClassName(self);
char *zombieClassName = NULL;
do {
//...
Class zombieClass = objc_getClass(zombieClassName);
objc_destructInstance(self); //關(guān)鍵
object_setClass(self, zombieClass);
} while (0);
if (zombieClassName != NULL)
{
free(zombieClassName);
}
}
而對(duì)于僵尸類(lèi)的實(shí)現(xiàn)岗屏,Zombie Object的實(shí)現(xiàn)簡(jiǎn)潔而且有效辆琅。不像其它人的實(shí)現(xiàn)那么臃腫。就只是申明了一個(gè)沒(méi)有任何方法的根類(lèi)而已这刷,所以任何消息發(fā)給它都會(huì)crash婉烟。
NS_ROOT_CLASS
@interface _NSZombie_ {
Class isa;
}
@end
所以我從Apple的源碼中提取出來(lái)的一套實(shí)現(xiàn)NSZombie,跟Zombie Object的實(shí)現(xiàn)保證完全一致暇屋,解決誤判的情況似袁。
Scribble
Scribble 工具能夠在alloc的時(shí)候填上0xAA,dealloc的時(shí)候填上0x55,就是對(duì)象釋放后在內(nèi)存上填上不可訪(fǎng)問(wèn)的數(shù)據(jù)昙衅,如果再次訪(fǎng)問(wèn)對(duì)象就會(huì)必現(xiàn)crash扬霜。
Bugly的這篇文章如何定位Obj-C野指針隨機(jī)Crash 就是采用這種方式提高crash率,來(lái)方便定位問(wèn)題而涉。
為了不限制在xcode中使用著瓶,自己在代碼中實(shí)現(xiàn)了類(lèi)似的邏輯。通過(guò)fishhook去hook free
函數(shù)的方法啼县,實(shí)現(xiàn)如下:
void safe_free(void* p){
size_tmemSiziee=malloc_size(p);
memset(p,0x55, memSiziee);
orig_free(p);
return;
}
雖然已經(jīng)給被釋放的對(duì)象寫(xiě)上了0x55材原,但是如果是內(nèi)存在被訪(fǎng)問(wèn)(觸發(fā)crash)之前被其它覆蓋了,則可能無(wú)法觸發(fā)crash季眷。 這種情況也不少見(jiàn)余蟹。 所以Bugly為了內(nèi)存不被覆蓋,就不再調(diào)用free來(lái)釋放這個(gè)內(nèi)存子刮。保持這個(gè)內(nèi)存一直在威酒。 這樣的原理就非常類(lèi)似Zombie Object
了。
制造crash的方式也是采用修改rsa指針的方式挺峡,當(dāng)對(duì)象收到消息的時(shí)候abort()兼搏。
Address Sanitizer
將malloc/free
函數(shù)進(jìn)行了替換。在malloc函數(shù)中額外的分配了禁止訪(fǎng)問(wèn)區(qū)域的內(nèi)存沙郭。 在free函數(shù)中將所有分配的內(nèi)存區(qū)域設(shè)為禁止訪(fǎng)問(wèn)佛呻,并放到了隔離區(qū)域的隊(duì)列中(保證在一定的時(shí)間內(nèi)不會(huì)再被malloc函數(shù)分配)。 如果訪(fǎng)問(wèn)到禁止訪(fǎng)問(wèn)的區(qū)域病线,就直接crash吓著。
對(duì)CPU影響2~5?, 增加內(nèi)存消耗 2~3?。
能夠檢查出來(lái)的問(wèn)題:
- 訪(fǎng)問(wèn)已經(jīng)dealloc的內(nèi)存/dealloc已經(jīng)dealloc的內(nèi)存
- dealloc還沒(méi)有alloc的內(nèi)存(但不能檢查出訪(fǎng)問(wèn)未初始化的內(nèi)存)
- 訪(fǎng)問(wèn)函數(shù)返回以后的棧內(nèi)存/訪(fǎng)問(wèn)作用域之外的棧內(nèi)存
- 緩沖區(qū)上溢出或下溢出,C++容器溢出(但不能檢查integer overflow)
不能用于檢查內(nèi)存泄漏送挑。有些文章說(shuō)ASan能檢查內(nèi)存泄漏是不對(duì)的绑莺,Google的LSan可以,但是Xcode的Asan不行惕耕。
Malloc Stack
之前介紹的工具都是提高崩潰概率纺裁,以拿到崩潰的對(duì)象和內(nèi)存地址。拿到崩潰的對(duì)象之后也很難定位司澎,因?yàn)楸罎⒌胤诫x釋放的地方已經(jīng)很遠(yuǎn)了欺缘。而且有些對(duì)象在工程中初始化了很多個(gè),不知道是對(duì)應(yīng)的哪個(gè)地方出了問(wèn)題挤安。所以如果能知道對(duì)象是在哪初始化的就好了谚殊。
Malloc Stack 能夠記錄下來(lái)所有對(duì)象的malloc調(diào)用時(shí)的堆棧信息。然后我們執(zhí)行命令:
script import lldb.macosx.heap
malloc_info --stack-history 0x7fbf0dd4f5c0
就可以在lldb中打印出來(lái)該對(duì)象初始化位置的堆棧信息蛤铜。
Malloc Stack但是有兩個(gè)巨大的缺點(diǎn)嫩絮,一個(gè)是只能在模擬器上使用丛肢,第二是沒(méi)有打印出dealloc的信息。如果想在真機(jī)上使用需要越獄剿干。
lzMalloc
公司內(nèi)部的大神開(kāi)發(fā)的的lldb插件蜂怎,基于Malloc Stack開(kāi)發(fā)的,通過(guò)調(diào)用私有函數(shù)拿到Malloc Stack記錄的數(shù)據(jù)置尔。能夠支持真機(jī)調(diào)試派敷,能夠打印出dealloc的堆棧信息。
能打印出dealloc的原因是hook了-dealloc方法撰洗,調(diào)用__disk_stack_logging_log_stack
函數(shù)記錄當(dāng)前的堆棧信息篮愉。
幾個(gè)野指針的例子
錯(cuò)誤的內(nèi)存修飾符
遇到的這個(gè)例子可能是比較經(jīng)典的野指針,崩潰日志中出現(xiàn)了各種各樣的表現(xiàn)差导。
第一種表現(xiàn)是dealloc對(duì)象時(shí)崩潰:
0 libsystem_kernel.dylib 0x252fac5c __pthread_kill + 4
1 libsystem_c.dylib 0x2528f0ac abort + 103
2 libsystem_malloc.dylib 0x25324ef6 free + 431
3 libobjc.A.dylib 0x24e13e08 object_dispose + 19
4 Foundation 0x25de3cf2 -[NSIndexPath dealloc] + 66
5 libobjc.A.dylib 0x24e24f66 objc_object::sidetable_release(bool) + 150
6 libsystem_blocks.dylib 0x25243ac2 _Block_release + 215
7 CoreFoundation 0x25583384 -[__NSArrayI dealloc] + 64
5 libobjc.A.dylib 0x24e24f66 objc_object::sidetable_release(bool) + 150
9 UIKit 0x29e934f2 __runAfterCACommitDeferredBlocks + 310
10 UIKit 0x29e9f7da __cleanUpAfterCAFlushAndRunDeferredBlocks + 90
11 UIKit 0x29bddb1c __afterCACommitHandler + 84
可以看到這里完全是系統(tǒng)library的崩潰试躏,跟工程代碼毫無(wú)關(guān)系,最開(kāi)始也是一頭霧水设褐。
這里只有兩個(gè)線(xiàn)索,一個(gè)是NSIndexPath
,另一個(gè)是只發(fā)生于10.3.3之前的iphone5機(jī)型上颠蕴。
因?yàn)?0.3.3是iphone5支持的最后一個(gè)版本,所以用戶(hù)量并不少助析。
第二種表現(xiàn)是objc_msgsend, isEqual:
是通過(guò)讀取ARM寄存器lr
獲取到的方法名犀被,這個(gè)是Bugly幫我們查到的。
0 libobjc.A.dylib 0x1a1b0dd6 objc_msgSend (isEqual:) + 15
1 UIKit 0x201afdfa -[UICollectionReusableView _setLayoutAttributes:] + 60
2 UIKit 0x209d0280 -[UICollectionView _applyLayoutAttributes:toView:] + 138
3 UIKit 0x209daf26 ___88-[UICollectionView _dequeueReusableViewOfKind:withIdentifier:forIndexPath:viewCategory:]_block_invoke + 28
4 UIKit 0x2015b5c2 +[UIView(Animation) performWithoutAnimation:] + 84
5 UIKit 0x209dae40 -[UICollectionView _dequeueReusableViewOfKind:withIdentifier:forIndexPath:viewCategory:] + 2156
6 UIKit 0x201af68a -[UICollectionView dequeueReusableCellWithReuseIdentifier:forIndexPath:] + 160
7 XXXXXXProject 0x00404c02 -[XXXXXXCollectionView collectionView:cellForItemAtIndexPath:] (XXXXXXClass.m:77)
8 UIKit 0x209cf850 -[UICollectionView _createPreparedCellForItemAtIndexPath:withLayoutAttributes:applyAttributes:isFocused:notify:] + 420
9 UIKit 0x201af5e0 -[UICollectionView _createPreparedCellForItemAtIndexPath:withLayoutAttributes:applyAttributes:] + 42
10 UIKit 0x201ad7f6 -[UICollectionView _updateVisibleCellsNow:] + 4076
11 UIKit 0x201a83d6 -[UICollectionView layoutSubviews] + 398
12 UIKit 0x2014b482 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1224
這里線(xiàn)索就比較豐富外冀,可以找到對(duì)應(yīng)的類(lèi)了寡键,XXXXXXProject
是我們的工程,明顯崩潰在UICollectionView
中雪隧。在重用collectionViewCell的過(guò)程中西轩,調(diào)用_setLayoutAttributes
的方法,在+60的位置調(diào)用了isEqual:
脑沿,經(jīng)過(guò)反編譯這個(gè)方法得知調(diào)用isEqual:
的對(duì)象的是UICollectionViewLayoutAttributes
(反編譯過(guò)程省略)藕畔。
這里也是只發(fā)生于10.3.3之前的iphone5機(jī)型上。所以基本確定是同一個(gè)問(wèn)題注服。
但是并沒(méi)有什么了卵用,正如之前所說(shuō)的措近,野指針崩潰的地方跟出錯(cuò)的地方相去甚遠(yuǎn)。
唯一能確定的地方熄诡,就是引起崩潰的對(duì)象是NSIndexPath
可很。
第三種表現(xiàn)比較奇怪凰浮,報(bào)[UITransitionView initialize] unrecognized selector
,這個(gè)類(lèi)一臉懵逼我抠。不知道在哪使用過(guò)
Exception Type: NSInvalidArgumentException(SIGABRT)
Exception Codes: -[UITransitionView initialize]: unrecognized selector sent to instance 0x165f22c0 at 0x1c4d1acc
Crashed Thread: 0
0 CoreFoundation 0x1cd03b3d ___exceptionPreprocess + 129
1 libobjc.A.dylib 0x1bf8b067 objc_exception_throw + 31
2 CoreFoundation 0x1cd08fd1 ___methodDescriptionForSelector + 1
3 CoreFoundation 0x1cd070c3 ____forwarding___ + 697
4 CoreFoundation 0x1cc2fdc8 _CF_forwarding_prep_0 + 24
5 libobjc.A.dylib 0x1bf8bbad _CALLING_SOME_+initialize_METHOD + 23
6 libobjc.A.dylib 0x1bf8bdf3 __class_initialize + 579
7 libobjc.A.dylib 0x1bf92c15 _lookUpImpOrForward + 173
8 libobjc.A.dylib 0x1bf92b65 __class_lookupMethodAndLoadCache3 + 27
9 libobjc.A.dylib 0x1bf991af __objc_msgSend_uncached + 15
10 UIKit 0x21f98167 -[UICollectionViewLayoutAttributes isEqual:] + 95
11 UIKit 0x21f97dfb -[UICollectionReusableView _setLayoutAttributes:] + 61
12 UIKit 0x227b8281 -[UICollectionView _applyLayoutAttributes:toView:] + 139
13 UIKit 0x227c2f27 ___88-[UICollectionView _dequeueReusableViewOfKind:withIdentifier:forIndexPath:viewCategory:]_block_invoke + 29
14 UIKit 0x21f435c3 +[UIView(Animation) performWithoutAnimation:] + 85
15 UIKit 0x227c2e41 -[UICollectionView _dequeueReusableViewOfKind:withIdentifier:forIndexPath:viewCategory:] + 2157
16 UIKit 0x21f9768b -[UICollectionView dequeueReusableCellWithReuseIdentifier:forIndexPath:] + 161
看下面的堆棧就發(fā)現(xiàn)還是同一個(gè)問(wèn)題,但是為啥會(huì)報(bào)這么奇怪的錯(cuò)袜茧? 這就是野指針的表現(xiàn)菜拓。這一塊內(nèi)存被別的東西覆蓋了。
實(shí)際上還有其它的表現(xiàn)纳鼎,但是比較具有代表性的就這三個(gè)了。從崩潰日志中只能得到有限的信息裳凸,一個(gè)是這個(gè)是野指針問(wèn)題。第二個(gè)是這個(gè)野指針對(duì)象很可能是一個(gè)NSIndexPath
對(duì)象(也不能完全確定)姨谷。
如果不知道是野指針的問(wèn)題逗宁,就很容易誤入歧途,花大量時(shí)間在研究UICollectionView
或者在研究UITransitionView
上梦湘。其實(shí)都是浪費(fèi)時(shí)間瞎颗,因?yàn)樵斐梢爸羔樀牡胤降胤揭呀?jīng)很遠(yuǎn)了。
正如Bugly這篇文章說(shuō)的捌议,定位野指針最重要還是增大野指針出現(xiàn)的概率哼拔。 所以這次我是采用Zombie Object瓣颅,并且限制在iPhone5 和 iOS10.3.3的情況下重現(xiàn)的倦逐。
經(jīng)過(guò)多次重現(xiàn)宫补,確定了是NSIndexPath
的問(wèn)題僻孝,而且所有的UICollectionView
和UITableView
都受到了影響守谓。所以我開(kāi)始懷疑是不是工程中有全局的代碼被hook了穿铆。果然不出所料:
- (void)forwardInvocation:(NSInvocation *)invocation
{
[invocation invokeWithTarget:self.target];
if (kiOS9Later) {
if ([NSStringFromSelector(invocation.selector) isEqualToString:@"collectionView:didSelectItemAtIndexPath:"]) {
//無(wú)痕打點(diǎn)
__unsafe_unretained UICollectionView *collectionView = nil;
id indexPath;
[invocation getArgument:&collectionView atIndex:2];
[invocation getArgument:&indexPath atIndex:3];
[FPPVHelper reportMTAEventId:[collectionView hotTagId] Index:[indexPath row] info:nil];
}
}
}
這是某一段神奇的打點(diǎn)代碼斋荞,不知道誰(shuí)寫(xiě)的荞雏。很明顯indexPath
此處的修飾符應(yīng)為__unsafe_unretained
,如果為strong
的話(huà)對(duì)象在這里就會(huì)被ARC釋放掉平酿,然而因?yàn)閭鬟f的是C指針凤优,其它地方的某個(gè)指針不知道這里釋放了,依然指向了這里筑辨。產(chǎn)生了野指針。
iOS9之前的delegate 崩潰
在iOS9之前的tableview的delegate和datasource都是assign
內(nèi)存修飾符的幸逆。iOS9之后才使用weak
暮现。
// iOS 8 之前
@property(nonatomic, assign) id<UITableViewDataSource> dataSource
@property(nonatomic, assign) id<UITableViewDelegate> delegate
// iOS 9 之后
@property(nonatomic, weak, nullable) id<UITableViewDataSource> dataSource
@property(nonatomic, weak, nullable) id<UITableViewDelegate> delegate
這種情況楚昭,如果delegate
比tableview
本身更早被釋放栖袋,此時(shí)的dataSource
就會(huì)成為一個(gè)野指針。常見(jiàn)的情況比如block調(diào)用延長(zhǎng)了tableview的生命周期塘幅,就可能會(huì)發(fā)生這種情況,導(dǎo)致野指針crash尿贫。 一般崩潰日志里是objc_msgsend + 15
的崩潰,崩潰在delegate或者datasource的方法里。
解決方法也很簡(jiǎn)單庆亡,在dealloc的時(shí)候把dataSource和delegate設(shè)為nil即可。
- (void)dealloc
{
_tableView.delegate = nil;
_tableView.dataSource = nil;
}
iOS9 之前的target-action崩潰
崩潰堆棧也是最常見(jiàn)的objc_msgSend身冀,這里可以看到是工程中hook的某個(gè)方法崩潰了
libobjc.A.dylib objc_msgSend (pv_gestureRecongizerAction:)
UIKit -[UIGestureRecognizer _updateGestureWithEvent:buttonEvent:]
UIKit ____UIGestureRecognizerUpdate_block_invoke662
UIKit __UIGestureRecognizerRemoveObjectsFromArrayAndApplyBlocks
UIKit __UIGestureRecognizerUpdate
SEGV_ACCERR
我們自己的代碼如下钝尸,就是在addGestureRecognizer
方法中加了一層調(diào)用搂根,加了一層target-action珍促。這相當(dāng)于是給gestureRecognizer
加了兩個(gè)target-action
-(void)pv_addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer {
[gestureRecognizer addTarget:self action:@selector(pv_gestureRecongizerAction:)];
[self pv_addGestureRecognizer:gestureRecognizer];
}
由于target對(duì)于gesture來(lái)說(shuō)在iOS8上也是類(lèi)似assign的剩愧,所以這里就是self被釋放了猪叙,變成野指針了仁卷,但是gestureRecognizer的target依然指向了self的內(nèi)存穴翩。 當(dāng)self已經(jīng)被釋放了锦积,但是gestureRecognizer還沒(méi)被釋放的時(shí)候就會(huì)發(fā)生這種情況芒帕。
總結(jié)
野指針定位有幾個(gè)關(guān)鍵:
- 第一是意識(shí)到這是野指針的問(wèn)題:
Mach Exception
大多數(shù)都是野指針的問(wèn)題,崩潰日志里最多見(jiàn)objc_msgSend
和unrecognized selector sent to
等等背蟆。而且往往跟iOS SDK版本和iphone型號(hào)有關(guān)。 認(rèn)識(shí)到野指針的問(wèn)題后哮幢,就不必要拘泥于崩潰日志,因?yàn)楸罎⒌牡胤诫x崩潰的原因比較遠(yuǎn)了橙垢。 - 第二是盡可能重現(xiàn)。利用
Zombie Object/Scribble/Aasn
都可以柜某。個(gè)人認(rèn)為自己實(shí)現(xiàn)的Zombie Object最好嗽元,既可以脫離Xcode debug的限制,使用又比較簡(jiǎn)單还棱。 - 第三是根據(jù)野指針指向的對(duì)象來(lái)判斷出錯(cuò)的位置载慈,而不是崩潰的方法珍手。因?yàn)楸罎⒌姆椒x崩潰的原因比較遠(yuǎn)了,但是野指針指向的對(duì)象多半還是出錯(cuò)的對(duì)象(有時(shí)也可能被覆蓋了)辞做。
- 第四是利用
malloc stack/lzMalloc
找到野指針指向?qū)ο蟪跏蓟奈恢煤蚫ealloc的位置琳要,判斷是否過(guò)早釋放等。