iOS底層探索之對(duì)象原理(三)

前言

iOS底層探索之對(duì)象原理(二)我們了解到 isa是一個(gè)聯(lián)合體位域,ISA_BITFIELD存儲(chǔ)了類(lèi)的一些信息,本文我們將繼續(xù)探索isa是如何關(guān)聯(lián)對(duì)象與類(lèi)的呢?以及isa的走位分析

alloc流程

iOS底層探索之對(duì)象原理(一)我們探索了《alloc底層原理》知道給一個(gè)對(duì)象發(fā)送alloc消息,在底層源碼會(huì)來(lái)到_objc_rootAlloc,然后進(jìn)入callAlloc。但其實(shí)這里漏掉了一個(gè)過(guò)程,就是objc_alloc

dyld在加載Mach-O二進(jìn)制文件的時(shí)候,會(huì)進(jìn)行符號(hào)綁定的操作,也就是說(shuō)sel_alloc綁定到了 objc_alloc上面去了。 這一步其實(shí)沒(méi)有真正開(kāi)源。

不過(guò)我們?cè)?libobjc 源碼中全局搜索 objc_alloc 即可發(fā)現(xiàn)如下代碼:

static void 
fixupMessageRef(message_ref_t *msg)
{    
    msg->sel = sel_registerName((const char *)msg->sel);

    if (msg->imp == &objc_msgSend_fixup) { 
        if (msg->sel == SEL_alloc) {
            msg->imp = (IMP)&objc_alloc;
        } 
        /* 省略下面的代碼 */
    } 
}

fixupMessageRef 的調(diào)用則是在 _read_images 也就是 dyld 讀取我們的鏡像文件的時(shí)候,什么意思呢,這里我們?cè)倏?_read_images 的源碼:

#if SUPPORT_FIXUP
    // Fix up old objc_msgSend_fixup call sites
    for (EACH_HEADER) {
        message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
        if (count == 0) continue;

        if (PrintVtables) {
            _objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
                         "call sites in %s", count, hi->fname());
        }
        for (i = 0; i < count; i++) {
            fixupMessageRef(refs+i);
        }
    }

    ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
#endif

從代碼不難看出,我們?cè)谧x取鏡像文件的時(shí)候,判斷如果需要 fixup ,如果需要的話(huà),我們就調(diào)用 fixupMessageRef ,然后在 fixupMessageRef 內(nèi)部,我們判斷當(dāng)前消息的 SEL 是否是 SEL_alloc,如果是的話(huà)就替換其 IMPobjc_alloc 。這一流程只會(huì)走一次,也就是說(shuō) objc_alloc 只會(huì)走一次。

用簡(jiǎn)單的流程圖表示如下:
alloc符號(hào)綁定流程.png

isa的走位分析

我們都知道對(duì)象可以創(chuàng)建多個(gè),但是類(lèi)是否可以創(chuàng)建多個(gè)呢?
答案很簡(jiǎn)單,一個(gè)。那么如果來(lái)驗(yàn)證呢?

首先我們來(lái)張 isa 和 superclass 的走位圖:
isa走位圖.png

接著我們開(kāi)始如下驗(yàn)證:

//MARK: - 分析類(lèi)對(duì)象內(nèi)存存在個(gè)數(shù)
void lgTestClassNum(){
    Class class1 = [LGPerson class];
    Class class2 = [LGPerson alloc].class;
    Class class3 = object_getClass([LGPerson alloc]);
    Class class4 = [LGPerson alloc].class;
    NSLog(@"\n%p-\n%p-\n%p-\n%p",class1,class2,class3,class4);
}

// 打印輸出如下:
0x100002108-
0x100002108-
0x100002108-
0x100002108

所以我們就知道了類(lèi)在內(nèi)存中只會(huì)存在一份。

(lldb) x/4gx LGTeacher.class
0x100001420: 0x001d8001000013f9 0x0000000100b38140
0x100001430: 0x00000001003db270 0x0000000000000000
(lldb) po 0x001d8001000013f9
17082823967917874

(lldb) p 0x001d8001000013f9
(long) $2 = 8303516107936761
(lldb) po 0x100001420
LGTeacher

我們通過(guò)上面的打印,就發(fā)現(xiàn) 類(lèi)的內(nèi)存結(jié)構(gòu)里面的第一個(gè)結(jié)構(gòu)打印出來(lái)還是 LGTeacher,那么是不是就意味著 對(duì)象->類(lèi)->類(lèi) 這樣的死循環(huán)呢?這里的第二個(gè)類(lèi)其實(shí)是 元類(lèi)。是由系統(tǒng)幫我們創(chuàng)建的。這個(gè)元類(lèi)也無(wú)法被我們實(shí)例化。

也就是下面的這種關(guān)系: 對(duì)象 ——> 類(lèi)對(duì)象 ——> 元類(lèi)

那么好奇心來(lái)了,元類(lèi)的 isa 又是什么呢,在Xcode測(cè)試有以下結(jié)果:

isa走位.jpeg

isa走位 & 繼承關(guān)系結(jié)論

  • isa:對(duì)象 ——> 類(lèi)對(duì)象 ——> 元類(lèi) ——> 根元類(lèi) ——> 根元類(lèi)自己
  • superclass:NSObject 父類(lèi)是nil 、根元類(lèi)的父類(lèi)是 NSObject

對(duì)象的本質(zhì)

在我們認(rèn)知里面,OC 對(duì)象的本質(zhì)就是一個(gè)結(jié)構(gòu)體,這個(gè)結(jié)論在 libobjc 源碼的 objc-private.h 源文件中可以得到證實(shí)。

struct objc_object {
private:
    isa_t isa;

public:
    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();

    // getIsa() allows this to be a tagged pointer object
    Class getIsa();

    /* 省略其他的內(nèi)容 */
}

而對(duì)于對(duì)象所屬的類(lèi)來(lái)說(shuō),我們也可以在 objc-runtime-new.h 源文件中找到

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    
    /* 省略其他的內(nèi)容 */
}

也就是說(shuō) objc_class 內(nèi)存中第一個(gè)位置是 isa,第二個(gè)位置是 superclass,我們可以通過(guò) LLDB 打印內(nèi)存地址來(lái)驗(yàn)證:

很簡(jiǎn)單,我們只需要使用 clang 的一個(gè)命令來(lái)編譯我們的 OC 源文件即可。

// 編譯底層源碼
clang -rewrite-objc main.m -o main.cpp

// 存在UIkit系統(tǒng)其他動(dòng)態(tài)庫(kù)引用問(wèn)題 則使用:
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk ViewController.m
// xcrun xcode 命令
xcrun -sdk iphonesimulator clang -rewrite-objc ViewController.m
xcrun -sdk iphoneos clang -rewrite-objc ViewController.m

clang -rewrite-objc main.m -o main.cpp 這行命令會(huì)把我們的 main.m 文件編譯成 C++ 格式,輸出為 main.cpp。

cpp.png

通過(guò)觀察,我們可以看到有一個(gè) NSObject_IVARS 這個(gè)其實(shí)是 NSObject 里面的成員變量。

  • 成員變量 (不會(huì)生成 getter 和 setter)
  • 實(shí)例變量是一種特殊的成員變量,是由類(lèi)聲明而來(lái)
  • 屬性 (LLVM 會(huì)幫我們自動(dòng)生成 getter 和 setter )

附上isa初始化圖

isa初始化.png

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

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