iOS開發之runtime(5):allocWithZone剖析

logo

本系列博客是本人的源碼閱讀筆記,如果有 iOS 開發者在看 runtime 的,歡迎大家多多交流。為了方便討論,本人新建了一個微信群(iOS技術討論群),想要加入的,請添加本人微信:zhujinhui207407,【加我前請備注:ios 】,本人博客http://www.kyson.cn 也在不停的更新中,歡迎一起討論

本文完整版詳見筆者小專欄:https://xiaozhuanlan.com/runtime

前言

上篇文章中我們有提到,alloc函數在一定條件下最終會調用callAlloc(Class cls, bool checkNil, bool allocWithZone=false)方法,而該方法中有一段這樣的判斷條件(以下稱為邏輯A):

if (fastpath(!cls->ISA()->hasCustomAWZ())) 

意思就是如果該類實現了allocWithZone方法,那么就不會走if里的邏輯,直接走以下邏輯(以下稱為邏輯B):

if (allocWithZone) return [cls allocWithZone:nil];

為了驗證這個判斷,筆者做了個實驗,在main.m中鍵入以下代碼:

@interface Person :NSObject
@end

@implementation Person
+(instancetype)allocWithZone:(struct _NSZone *)zone
{
    return nil;
}
@end

int main(int argc, const char * argv[]) {
    Person *p = [[Person alloc] init];
    return 0;
}

會發現走邏輯B。那大家肯定能猜到,肯定是什么時候調用了某個函數,導致cls->ISA()->hasCustomAWZ()變成了true。本文將帶大家了解CustomAWZ這個“屬性”設置的過程。

分析

首先 我們了解一下這段代碼:

cls->ISA()->hasCustomAWZ()

可知,這段代碼分兩步:

  • cls->ISA()
inline Class 
objc_object::ISA() 
{
    assert(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    return (Class)(isa.bits & ISA_MASK);
#endif
}

由前面的文章分析可知,該函數可以簡寫為

inline Class 
objc_object::ISA() 
{
    assert(!isTaggedPointer()); 
    return (Class)(isa.bits & ISA_MASK);
}

因此,這段代碼有是我們熟悉的,拿到了isa_tshiftcls位域,改位域存儲了對象的內存區域。

  • hasCustomAWZ()
    查看其實現,可以發現如下代碼:
bool hasCustomAWZ() {
    return ! bits.hasDefaultAWZ();
}

查看其附近的函數,可以看到如下幾個函數:

#if FAST_HAS_DEFAULT_AWZ
    bool hasDefaultAWZ() {
        return getBit(FAST_HAS_DEFAULT_AWZ);
    }
    void setHasDefaultAWZ() {
        setBits(FAST_HAS_DEFAULT_AWZ);
    }
    void setHasCustomAWZ() {
        clearBits(FAST_HAS_DEFAULT_AWZ);
    }
#else
    bool hasDefaultAWZ() {
        return data()->flags & RW_HAS_DEFAULT_AWZ;
    }
    void setHasDefaultAWZ() {
        data()->setFlags(RW_HAS_DEFAULT_AWZ);
    }
    void setHasCustomAWZ() {
        data()->clearFlags(RW_HAS_DEFAULT_AWZ);
    }
#endif

其中,宏 FAST_HAS_DEFAULT_AWZ在文件objc-runtime-new.h中有定義:

// objc-runtime-new.h
// Values for class_rw_t->flags or class_t->bits
// These flags are optimized for retain/release and alloc/dealloc
// 64-bit stores more of them in class_t->bits to reduce pointer indirection.
#if !__LP64__
...
#elif 1
...

#else 
// summary bit for fast alloc path: !hasCxxCtor and 
// !instancesRequireRawIsa and instanceSize fits into shiftedSize
// hasCxxCtor是判斷當前class或者superclass 是否有.cxx_construct構造方法的實現。
// FAST_ALLOC means
//   FAST_HAS_CXX_CTOR is set
//   FAST_REQUIRES_RAW_ISA is not set
//   FAST_SHIFTED_SIZE is not zero
// FAST_ALLOC does NOT check FAST_HAS_DEFAULT_AWZ because that 
// bit is stored on the metaclass.
#define FAST_ALLOC   (1UL<<50)
// class or superclass has default alloc/allocWithZone: implementation
// Note this is is stored in the metaclass.
#define FAST_HAS_DEFAULT_AWZ    (1UL<<48)
#end

首先 if !__ LP64 __ 是處理32位系統的,這里暫時不考慮,然后這里需要注意的是 elif 1,就是else if(1) 的簡寫!
也就是說,#else 不會被編譯了,那么上面兩個條件 FAST_ALLOC 和 FAST_HAS_DEFAULT_AWZ就不成立。
因此以上代碼可以簡寫為:

bool hasDefaultAWZ() {
    return data()->flags & RW_HAS_DEFAULT_AWZ;
}
void setHasDefaultAWZ() {
    data()->setFlags(RW_HAS_DEFAULT_AWZ);
}
void setHasCustomAWZ() {
    data()->clearFlags(RW_HAS_DEFAULT_AWZ);
}

這里不對這幾個函數做深入分析了,只是給大家提供一個參考:是否setHasCustomAWZ函數就是導致hasCustomAWZ為true的原因呢?帶著這個疑問,我們全局搜一下setHasCustomAWZ,終于在如下代碼中找到了答案(代碼位于文件objc-runtime-new.mm 1400多行):

if (supercls->hasCustomAWZ()) {
    subcls->setHasCustomAWZ(true);
}

為了驗證筆者的想法,筆者添加了日志函數用于驗證:

if (0 == strncmp(subcls->nameForLogging(), "Person", 5)) {
    printf("subcls:%s,superclass:%s hasCustomAllocZone:%d\n",subcls->nameForLogging(),supercls->nameForLogging(),subcls->ISA()->hasCustomAWZ());
}
if (supercls->hasCustomAWZ()) {
    subcls->setHasCustomAWZ(true);
}
if (0 == strncmp(subcls->nameForLogging(), "Person", 5)) {
    printf("=====subcls:%s,superclass:%s hasCustomAllocZone:%d\n",subcls->nameForLogging(),supercls->nameForLogging(),subcls->ISA()->hasCustomAWZ());
}

可以看到日志臺打印如下結果:


image

至此,終于驗證了筆者想法。

nameForLogging()方法是打印類名的方法,這里簡單了解一下即可。后面的文章將給于詳細的解釋。

總結

本文通過alloc函數中調用的函數callAlloc(Class cls, bool checkNil, bool allocWithZone=false)的分支if (fastpath(!cls->ISA()->hasCustomAWZ()))猜測allocWithZone影響的是結構體class_rw_t中的flags字段。至于這個結構體的詳細分析以及flags的含義,筆者會在稍后的文章中給出。


本文完整版詳見筆者小專欄:https://xiaozhuanlan.com/runtime


廣告

我的首款個人開發的APP壁紙寶貝上線了,歡迎大家下載。

壁紙寶貝

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容