刷微博看到大佬分析評價了一個庫對野指針攔截處理欧漱,通讀之后若有所思她按,隨即點了收藏烛谊。
大概過了兩周攀例,重新翻看這條分析,發(fā)現(xiàn)自己并沒有完整理解消化了這個野指針攔截原理皿曲,之前收藏的時候評論了原博唱逢,希望博主貼個源碼地址學(xué)習(xí)一下~然而大佬并沒有回復(fù)吴侦,于是自己試著用截圖里的keyword去搜了下,最終還是找到了--> JJException
這個庫的野指針攔截處理的原理其實就是模仿Xcode的僵尸對象捕獲的功能(即Zombie)
套用大佬的話:hook了dealloc坞古,然后對該對象的內(nèi)存做個廢棄的標(biāo)記但是不釋放备韧,之后替換該對象所屬的類,對替換的類添加一個通用的方法識別绸贡,只要有調(diào)用任何一個方法就屬于野指針盯蝴,日志上報即可。
那么听怕,我們從代碼層面看一下是具體是怎么實現(xiàn)的:
@implementation NSObject (ZombieHook)
+ (void)jj_swizzleZombie{
[self jj_swizzleInstanceMethod:@selector(dealloc) withSwizzleMethod:@selector(hookDealloc)];
}
- (void)hookDealloc{
Class currentClass = self.class;
//Check black list(如果該類在黑名單中捧挺,則還是走原有dealloc方法)
if (![[[JJExceptionProxy shareExceptionProxy] blackClassesSet] containsObject:currentClass]) {
[self hookDealloc];
return;
}
//Check the array max size
if ([JJExceptionProxy shareExceptionProxy].currentClassSize > MAX_ARRAY_SIZE) {
id object = [[JJExceptionProxy shareExceptionProxy] objectFromCurrentClassesSet];
[[JJExceptionProxy shareExceptionProxy] removeCurrentZombieClass:object_getClass(object)];
object?free(object):nil;
}
objc_destructInstance(self);//注1:銷毀該對象的引用關(guān)系
object_setClass(self, [JJZombieSub class]);//注2:替換該對象所屬的類
[[JJExceptionProxy shareExceptionProxy] addCurrentZombieClass:currentClass];
}
@end
首先用Category的方式Hook NSObject的dealloc方法,野指針發(fā)生的前提是:這個對象的內(nèi)存已經(jīng)被系統(tǒng)回收了尿瞭,對象調(diào)用方法時指向的是一個已經(jīng)被回收了的闽烙,無效的內(nèi)存地址。對象被回收必走dealloc方法声搁,因此Hook這個方法是一個很好的切入點黑竞。
接著通過runtime替換該對象所屬的類,注意這是一個通用的類(JJZombieSub)疏旨。所有的野指針方法的Class都會被替換成JJZombieSub很魂。
替換該對象所屬的類干啥?接下來我們把目光移到JJZombieSub這個替代類檐涝。
@interface JJZombieSub : NSObject
@end
@implementation JJZombieSub
- (id)forwardingTargetForSelector:(SEL)selector{
NSMethodSignature* sign = [self methodSignatureForSelector:selector];
if (!sign) {//注3:獲取所調(diào)方法的簽名遏匆,如果簽名不存在則說明該Target下沒有這個selector。
id stub = [[ZombieSelectorHandle new] autorelease];
[stub setFromObject:self];//注4:生成一個后續(xù)處理的對象谁榜,并綁定原類的信息幅聘,用于上傳日志時標(biāo)記是哪個類出現(xiàn)了野指針。
class_addMethod([stub class], selector, (IMP)unrecognizedSelectorZombie, "v@:");
return stub;//注5:將所調(diào)方法添加到后續(xù)處理類上ZombieSelectorHandle窃植,并替換該方法的IMP(即最終執(zhí)行unrecognizedSelectorZombie方法)
}
return [super forwardingTargetForSelector:selector];
}
@end
這個通用替代類里重寫了forwardingTargetForSelector:方法帝蒿,這個方法屬于消息轉(zhuǎn)發(fā)機(jī)制里的第二步,重寫這個方法可以動態(tài)的去替換所調(diào)方法的Target巷怜。
這里重寫這個方法就是為了將野指針的方法重定向到后續(xù)處理對象上執(zhí)行葛超。
@interface ZombieSelectorHandle : NSObject
@property(nonatomic,readwrite,assign)id fromObject;
@end
@implementation ZombieSelectorHandle
void unrecognizedSelectorZombie(ZombieSelectorHandle* self, SEL _cmd){
}
@end
這個ZombieSelectorHandle就是最終要處理野指針、上傳日志的類延塑。unrecognizedSelectorZombie方法有兩個參數(shù):后續(xù)處理對象和野指針方法的SEL巩掺,后續(xù)處理對象的fromObject屬性即是野指針方法所屬的類。
拿到了發(fā)生野指針的類名和方法名页畦,在unrecognizedSelectorZombie方法中去上傳日志胖替,以便于后續(xù)的排查。
代碼層面的流程就出來了:
-->Hook NSObject的dealloc方法
-->將野指針發(fā)生的類替換為JJZombieSub通用類
-->消息轉(zhuǎn)發(fā)時重定向野指針方法的類為ZombieSelectorHandle處理類、同時替換野指針發(fā)生的方法為通用的unrecognizedSelectorZombie方法独令。
--> ZombieSelectorHandle對象的unrecognizedSelectorZombie進(jìn)行日志上傳端朵。
為什么作者要轉(zhuǎn)換兩次類JJZombieSub、ZombieSelectorHandle而不是直接全部在JJZombieSub中處理燃箭?
我猜測應(yīng)該是基于設(shè)計層面去考慮的冲呢,JJZombieSub只負(fù)責(zé)攔截、ZombieSelectorHandle負(fù)責(zé)后續(xù)處理招狸,互不干擾各司其職敬拓。
一點思考:
在看到大佬的那條微博前,自己用過Zombie裙戏,但是基本沒去考慮過系統(tǒng)的Zombie是怎么去處理的乘凸,對原理的好奇心還遠(yuǎn)遠(yuǎn)不夠,好奇心是進(jìn)步的源動力啊累榜,fighting~