iOS中檢測Zoombie對象的具體實現(xiàn)

iOS中檢測Zoombie對象的具體實現(xiàn)

我們知道,如果在XCode中開啟了Zoombie Objects。如圖。

1.png

那么在一個對象釋放后,再次給該對象發(fā)送消息,在Xcode控制臺中,可看到如下打印信息。這些信息可以幫助我們定位問題。

ZoombieDemo[12275:2841478] *** -[Test test]: message sent to deallocated instance 0x60800000b000

那么究竟XCode是如何實現(xiàn)僵尸對象的檢查的,我們將來一一揭曉。

實現(xiàn)原理

在《Effective Objective-C 》一書中有提到過僵尸指針的實現(xiàn)方式。

通過hook NSObject的dealloc的方法,在一個對象要釋放的時候,通過objc_duplicateClass復(fù)制_NS_Zombie類,生成_NS_Zombie_OriginaClass,并且將當前對象的isa指向新生成的類。這塊內(nèi)存不會釋放。

因為在給該對象發(fā)消息時,_NS_Zombie_OriginaClass并未實現(xiàn)原有類的方法,所以會走完整的消息轉(zhuǎn)發(fā)。所以我們能取出具體的OriginaClass(去掉_NS_Zombie),當前sel,打印出來。

[class seletor]:message sent to deallocated instance 0x22909"

簡單來說,就是將對象指向一個新的類,因為新類里面并沒有原有類方法的實現(xiàn),所以必定會走到消息轉(zhuǎn)發(fā)中。

以上說的是動態(tài)生成新的類,類名是通過固定前綴拼接而成,將isa指向該類。其實還有一種方式,就是指向固定的類,原有類名通過關(guān)聯(lián)對象的方式來存儲。

既然知道了原理,可以動手實現(xiàn)一下。

動手實現(xiàn)

首先是hook dealloc方法。在NSObject+HookDealloc中實現(xiàn)。

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        SEL originalSelector = NSSelectorFromString(@"dealloc");
        SEL swizzledSelector = @selector(swizzledDealloc);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        
        BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
        if (success) {
            class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

動態(tài)生成新的類

在swizzledDealloc中,我們通過"Zoombie_"拼接原始類名,得到一個新的類名。然后生成該類,添加
forwardingTargetForSelector的實現(xiàn)。便于在消息轉(zhuǎn)發(fā)的時候得到調(diào)用信息。

NSString *Zoombie_Class_Prefix = @"Zoombie_";

// 指向動態(tài)生成的類,用Zoombie拼接原有類名
NSString *className = NSStringFromClass([self class]);

NSString *zombieClassName = [Zoombie_Class_Prefix stringByAppendingString: className];
    
Class zombieClass = NSClassFromString(zombieClassName);
if(zombieClass) return;
    
zombieClass = objc_allocateClassPair([NSObject class], [zombieClassName UTF8String], 0);
    
objc_registerClassPair(zombieClass);
class_addMethod([zombieClass class], @selector(forwardingTargetForSelector:), (IMP)forwardingTargetForSelector, "@@:@");

object_setClass(self, zombieClass);

forwardingTargetForSelector的方法實現(xiàn),原始類名,去掉前綴即可得到。因為這里已經(jīng)是調(diào)用到已釋放對象的方法,我們直接abort掉,程序?qū)⒈罎ⅰ?/p>

id forwardingTargetForSelector(id self, SEL _cmd, SEL aSelector) {
    NSString *className = NSStringFromClass([self class]);
    NSString *realClass = [className stringByReplacingOccurrencesOfString:Zoombie_Class_Prefix withString:@""];
    NSLog(@"[%@ %@] message sent to deallocated instance %@", realClass, NSStringFromSelector(aSelector), self);
    abort();
}

指向固定類

指向已有的ZoombieObject類,類名存在關(guān)聯(lián)對象中。

 // 指向固定的類,原有類名存儲在關(guān)聯(lián)對象中
NSString *originClassName = NSStringFromClass([self class]);
objc_setAssociatedObject(self, "OrigClassNameKey", originClassName, OBJC_ASSOCIATION_COPY_NONATOMIC);

object_setClass(self, [ZoombieObject class]);

同上,在ZoombieObject中實現(xiàn)forwardingTargetForSelector方法,可以得到調(diào)用信息。原始類名通過關(guān)聯(lián)對象獲取。

- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"[%@ %@] message sent to deallocated instance %@", objc_getAssociatedObject(self, "OrigClassNameKey"), NSStringFromSelector(aSelector), self);

    abort();
}

forwardingTargetForSelector是消息轉(zhuǎn)發(fā)的第二步,我們也可以不在這里處理,等到最后一步forwardInvocation,不過要生成方法簽名,要略微復(fù)雜些。

要想走到forwardInvocation,methodSignatureForSelector返回不能是空。這里我們返回了StubProxy類中stub的方法簽名(已經(jīng)定義好的類和方法),最后就回走到forwardInvocation,通過invocation.selector可得到當前調(diào)用方法名。通過關(guān)聯(lián)對象獲取到原始類名。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *sig = [super methodSignatureForSelector:aSelector];
    if (!sig) {
        sig = [StubProxy instanceMethodSignatureForSelector:@selector(stub)];
    }
    
    return sig;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"[%@ %@] message sent to deallocated instance %@", objc_getAssociatedObject(self, "OrigClassNameKey"), NSStringFromSelector(anInvocation.selector), self);
}

這樣,一個簡單的檢測僵尸指針的方案就實現(xiàn)了。

demo在此。

兩種方式都實現(xiàn)了,可通過調(diào)整NSObject+HookDealloc中,swizzledSelector的值來切換。my_dealloc是指向動態(tài)類,swizzledDealloc是指向固定類。

SEL swizzledSelector = @selector(my_dealloc);

在App運行起來后,點擊button,即可觸發(fā)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,068評論 0 9
  • 版權(quán)聲明本文轉(zhuǎn)自網(wǎng)易杭州前端技術(shù)部公眾號,由作者授權(quán)發(fā)布。 前言 大白(Baymax),迪士尼動畫《超能陸戰(zhàn)隊》中...
    XueYongWei閱讀 2,140評論 2 11
  • p2p安全排行榜_華融道理財 華融道理財你的首選~ p2p安全排行榜_華融道理財 ECHO 處于關(guān)閉狀態(tài)。 余額寶...
    魚蒲醋17095閱讀 250評論 0 0
  • 十月份以來圍繞著一件事,內(nèi)心的波浪時時翻滾,同樣的事以前不走心的樂此不疲的做下去,是因為覺得還有希望,一切只是暫時...
    有個內(nèi)在小人的我閱讀 175評論 0 0

友情鏈接更多精彩內(nèi)容