深入理解Runtime中的isa

objc_object

Objective-C 所有對象都是 C 語言結(jié)構(gòu)體objc_object,這個結(jié)構(gòu)體中包含一個isa成員變量,不是一個普通的指針,是一個isa_t結(jié)構(gòu)體。

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();

    // initIsa() should be used to init the isa of new objects only.
    // If this object already has an isa, use changeIsa() for correctness.
    // initInstanceIsa(): objects with no custom RR/AWZ
    // initClassIsa(): class objects
    // initProtocolIsa(): protocol objects
    // initIsa(): other objects
    void initIsa(Class cls /*nonpointer=false*/);
    void initClassIsa(Class cls /*nonpointer=maybe*/);
    void initProtocolIsa(Class cls /*nonpointer=maybe*/);
    void initInstanceIsa(Class cls, bool hasCxxDtor);
}

isa_t

因為iPhone5s之后的設(shè)備是在arm64架構(gòu)下,所以截取arm64架構(gòu)下的代碼,這個結(jié)構(gòu)體是蘋果經(jīng)過優(yōu)化的

armv64: iPhoneX, iPhone 5s-8, iPad Air?—?iPad Pro
armv7 : iPhone3Gs-5c, iPad WIFI(4th gen)
armv6 : iPhone?—?iPhone3G
the above if for real devices
i386 : 32-bit simulator
x86_64 : 64-bit simulator

i386是針對intel通用微處理器32位處理器
x86_64是針對x86架構(gòu)的64位處理器

模擬器32位處理器測試需要i386架構(gòu)
模擬器64位處理器測試需要x86_64架構(gòu)
真機32位處理器需要armv7,或者armv7s架構(gòu)
真機64位處理器需要arm64架構(gòu)

為何要進行優(yōu)化呢,進行了怎樣的優(yōu)化,看看Friday Q&A 2013-09-27: ARM64 and You怎么說的

盡管指針為64位,但在實際使用中,這些位數(shù)并不是都用上了。例如X86-64的Mac OS X系統(tǒng)僅使用了其中的47位。而ARM64上占用得更少,目前只有33位。只要未被系統(tǒng)全部占用,這些指針就能用于存儲數(shù)據(jù)。這是Objective-C Runtime演進史上最重要的變化之一。

Objective-C對象是連續(xù)的內(nèi)存塊,這個內(nèi)存塊中第一個指針大小的部分稱為ISA。一般來說,ISA是一個指向該對象所屬類的指針。不過這么大的空間僅作為指針有點兒浪費,尤其是在64位CPU上。運行iOS的ARM64目前僅使用了一個指針的33位,而其余31位則另作他用。另外,類 指針還需要對齊,這就釋放了另外3位,于是ISA指針中共有34位可另作他用。蘋果的ARM64 Runtime正是利用了這一點使性能有了大幅提升。

union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {
        uintptr_t nonpointer        : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 19;
#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)
    };
}

Hamster Emporium: [objc explain]: Non-pointer isa - Sealie Software一文對這些bits做了如下解釋

bits 作用
nonpointer 0表示普通isa,1表示non-pointe isa
has_assoc 對象具有或曾經(jīng)具有關(guān)聯(lián)的引用。沒有關(guān)聯(lián)引用的對象可以更快地析構(gòu)。
has_cxx_dtor 對象有一個C ++或ARC析構(gòu)函數(shù)。 沒有析構(gòu)函數(shù)的對象可以更快地解除分配。
shiftcls 類指針的非零位
magic 等于0xd2,調(diào)試器使用它來判斷對象是否完成了初始化
weakly_referenced 對象有或者曾經(jīng)有過ARC的weak對象,如果沒有,析構(gòu)更快
deallocating 對象正在析構(gòu)
has_sidetable_rc 對象的應(yīng)用計數(shù)太大,無法內(nèi)斂存儲
extra_rc 這個數(shù)值加1為對象的引用計數(shù).(例如,如果extra_rc為5,則對象的實際引用計數(shù)為6.)

initIsa

objc_object的代碼中我們可以看到有好幾初始化方法,最后都會走到下面這個方法,我只是截取了部分我們需要的

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    assert(!isTaggedPointer()); 
    
    if (!nonpointer) {
        isa.cls = cls;
    } else {
        assert(!DisableNonpointerIsa);
        assert(!cls->instancesRequireRawIsa());

        isa_t newisa(0);

        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
        isa = newisa;
    }
}

有一個地方不好理解

newisa.shiftcls = (uintptr_t)cls >> 3

這里我們可以看到是將類地址右移三位得到有效的33位shiftcls位,Friday Q&A 2013-09-27: ARM64 and You從 NSObject 的初始化了解 isa 解釋:

使用整個指針大小的內(nèi)存來存儲 isa 指針有些浪費,尤其在 64 位的 CPU 上。在 ARM64 運行的 iOS 只使用了 33 位作為指針(與結(jié)構(gòu)體中的 33 位無關(guān),Mac OS 上為 47 位),而剩下的 31 位用于其它目的。類的指針也同樣根據(jù)字節(jié)對齊了,每一個類指針的地址都能夠被 8 整除,也就是使最后 3 bits 為 0,為 isa 留下 34 位用于性能的優(yōu)化。

絕大多數(shù)機器的架構(gòu)都是 byte-addressable 的,但是對象的內(nèi)存地址必須對齊到字節(jié)的倍數(shù),這樣可以提高代碼運行的性能,在 iPhone5s 中虛擬地址為 33 位,所以用于對齊的最后三位比特為 000,我們只會用其中的 30 位來表示對象的地址

objc_object::ISA()

objc-object.h文件中

inline Class 
objc_object::ISA() 
{
    return (Class)(isa.bits & ISA_MASK);
}
define ISA_MASK        0x0000000ffffffff8ULL

ffffffff8 = 1111 1111 1111 1111 1111 1111 1111 1111 1000共36位,isa.bits和&運算后就拿到33位shiftcls,也就是類指針。

objc_class

Objective-C 中類都是 C 語言的類objc_class。

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
    
    class_rw_t *data() { 
        return bits.data();
    }
}

代碼是不是看著有點奇怪,objc_class繼承自objc_object,說明在Objective-C 中類也是一個對象。我們先來看看objc_class的結(jié)構(gòu):

  1. isa:因為objc_class繼承自objc_object,所以它包含一個isa成員變量,指向(MetaClass)元類
  2. superclass:指向當(dāng)前類的父類
  3. cache:用來緩存指針和vtable(virtual table虛函數(shù)表)。Runtime運行時系統(tǒng)庫實現(xiàn)了一種自定義的虛函數(shù)表分派機制。這個表是專門用來提高性能和靈活性的。用來存儲IMP類型的數(shù)組
  4. bits:class_rw_t 指針加上 rr/alloc 標(biāo)志,用來存儲類的屬性,方法,協(xié)議等信息

class_rw_t

class_data_bits_t的data是class_rw_t結(jié)構(gòu)體

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;
}

  1. methods:方法列表
  2. properties:屬性列表
  3. protocols:協(xié)議列表

class_ro_t

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

  1. instanceSize:instance對象占用的內(nèi)存空間
  2. name:類名
  3. baseMethodList:類的原始方法列表
  4. baseProtocols:類的原始屬性列表
  5. ivars:成員變量列表

我們看到class_rw_tclass_ro_t都有方法列表等,深入解析 ObjC 中方法的結(jié)構(gòu)對此作出的解釋:

在分析方法在內(nèi)存中的位置時,筆者最開始一直在嘗試尋找只讀結(jié)構(gòu)體 class_ro_t 中的 baseMethods 第一次設(shè)置的位置(了解類的方法是如何被加載的)。嘗試從 methodizeClass 方法一直向上找,直到 _obj_init 方法也沒有找到設(shè)置只讀區(qū)域的 baseMethods 的方法。

而且在 runtime 初始化之后,realizeClass 之前,從 class_data_bits_t 結(jié)構(gòu)體中獲取的 class_rw_t 一直都是錯誤的,這個問題在最開始非常讓我困惑,直到后來在 realizeClass 中發(fā)現(xiàn)原來在這時并不是 class_rw_t 結(jié)構(gòu)體,而是class_ro_t,才明白錯誤的原因。

后來突然想到類的一些方法、屬性和協(xié)議實在編譯期決定的(baseMethods 等成員以及類在內(nèi)存中的位置都是編譯期決定的),才感覺到豁然開朗。

類在內(nèi)存中的位置是在編譯期間決定的,在之后修改代碼,也不會改變內(nèi)存中的位置。

類的方法、屬性以及協(xié)議在編譯期間存放到了“錯誤”的位置,直到 realizeClass 執(zhí)行之后,才放到了 class_rw_t 指向的只讀區(qū)域 class_ro_t,這樣我們即可以在運行時為 class_rw_t 添加方法,也不會影響類的只讀結(jié)構(gòu)。

在 class_ro_t 中的屬性在運行期間就不能改變了,再添加方法時,會修改 class_rw_t 中的 methods 列表,而不是 class_ro_t 中的 baseMethods,對于方法的添加會在之后的文章中分析。

class and metaclass

在前面的源碼中可以看到,對象通過isa找到類,同時類也是一種對象,類隱式的擁有isa,那這個isa指向哪里呢,元類。

static void objc_initializeClassPair_internal(Class superclass, const char *name, Class cls, Class meta)
{
    runtimeLock.assertWriting();

    class_ro_t *cls_ro_w, *meta_ro_w;
    
    cls->setData((class_rw_t *)calloc(sizeof(class_rw_t), 1));
    meta->setData((class_rw_t *)calloc(sizeof(class_rw_t), 1));
    cls_ro_w   = (class_ro_t *)calloc(sizeof(class_ro_t), 1);
    meta_ro_w  = (class_ro_t *)calloc(sizeof(class_ro_t), 1);
    cls->data()->ro = cls_ro_w;
    meta->data()->ro = meta_ro_w;

    // Set basic info

    cls->data()->flags = RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING;
    meta->data()->flags = RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING;
    cls->data()->version = 0;
    meta->data()->version = 7;

    cls_ro_w->flags = 0;
    meta_ro_w->flags = RO_META;
    if (!superclass) {
        cls_ro_w->flags |= RO_ROOT;
        meta_ro_w->flags |= RO_ROOT;
    }
    if (superclass) {
        cls_ro_w->instanceStart = superclass->unalignedInstanceSize();
        meta_ro_w->instanceStart = superclass->ISA()->unalignedInstanceSize();
        cls->setInstanceSize(cls_ro_w->instanceStart);
        meta->setInstanceSize(meta_ro_w->instanceStart);
    } else {
        cls_ro_w->instanceStart = 0;
        meta_ro_w->instanceStart = (uint32_t)sizeof(objc_class);
        cls->setInstanceSize((uint32_t)sizeof(id));  // just an isa
        meta->setInstanceSize(meta_ro_w->instanceStart);
    }

    cls_ro_w->name = strdupIfMutable(name);
    meta_ro_w->name = strdupIfMutable(name);

    cls_ro_w->ivarLayout = &UnsetLayout;
    cls_ro_w->weakIvarLayout = &UnsetLayout;

    meta->chooseClassArrayIndex();
    cls->chooseClassArrayIndex();

    // Connect to superclasses and metaclasses
    cls->initClassIsa(meta);
    if (superclass) {
        meta->initClassIsa(superclass->ISA()->ISA());
        cls->superclass = superclass;
        meta->superclass = superclass->ISA();
        addSubclass(superclass, cls);
        addSubclass(superclass->ISA(), meta);
    } else {
        meta->initClassIsa(meta);
        cls->superclass = Nil;
        meta->superclass = cls;
        addRootClass(cls);
        addSubclass(cls, meta);
    }

    cls->cache.initializeToEmpty();
    meta->cache.initializeToEmpty();
}
objc-isa-class-diagram.png

上圖實線是 superclass 指針,虛線是isa指針

  1. 對象調(diào)用實例方法時,是在對應(yīng)類對象及其繼承鏈上找方法。類對象調(diào)用類方法時,是在其元類及繼承鏈上找方法
  2. 所以元類的isa指針都指向根元類(NSObject),根元類的isa指針指向自己
  3. 所有的類方法都儲存在元類當(dāng)中
  4. NSObject 的超類為 nil

關(guān)于元類Classes and metaclasses What is a meta-class in Objective-C?解釋的很詳細

下面是What is a meta-class in Objective-C?的測試代碼

Class newClass =
    objc_allocateClassPair([NSError class], "RuntimeErrorSubclass", 0);
class_addMethod(newClass, @selector(report), (IMP)ReportFunction, "v@:");
objc_registerClassPair(newClass);

void ReportFunction(id self, SEL _cmd)
{
    NSLog(@"This object is %p.", self);
    NSLog(@"Class is %@, and super is %@.", [self class], [self superclass]);
 
    Class currentClass = [self class];
    for (int i = 1; i < 5; i++)
    {
        NSLog(@"Following the isa pointer %d times gives %p", i, currentClass);
        currentClass = object_getClass(currentClass);
    }
 
    NSLog(@"NSObject's class is %p", [NSObject class]);
    NSLog(@"NSObject's meta class is %p", object_getClass([NSObject class]));
}
id instanceOfNewClass =
    [[newClass alloc] initWithDomain:@"someDomain" code:0 userInfo:nil];
[instanceOfNewClass performSelector:@selector(report)];
[instanceOfNewClass release];

打印結(jié)果:

This object is 0x10010c810.
Class is RuntimeErrorSubclass, and super is NSError.
Following the isa pointer 1 times gives 0x10010c600
Following the isa pointer 2 times gives 0x10010c630
Following the isa pointer 3 times gives 0x7fff71038480
Following the isa pointer 4 times gives 0x7fff71038480
NSObject's class is 0x7fff710384a8
NSObject's meta class is 0x7fff71038480

觀察isa到達過的地址的值:

  • 對象的地址是 0x10010c810
  • 類的地址是 0x10010c600
  • 元類的地址是 0x10010c630
  • 根元類(NSObject的元類)的地址是 0x7fff71038480
  • NSObject元類的類是它本身

元類是 Class 對象的類。每個類(Class)都有自己獨一無二的元類(每個類都有自己第一無二的方法列表)。這意味著所有的類對象都不同。

元類總是會確保類對象和基類的所有實例和類方法。對于從NSObject繼承下來的類,這意味著所有的NSObject實例和protocol方法在所有的類(和meta-class)中都可以使用。

所有的meta-class使用基類的meta-class作為自己的基類,對于頂層基類的meta-class也是一樣,只是它指向自己而已。

從 NSObject 的初始化了解 isa
Hamster Emporium: [objc explain]: Non-pointer isa - Sealie Software
Testing if an arbitrary pointer is a valid Objective-C object
從 NSObject 的初始化了解 isa
Pro Multithreading and Memory Management for iOS and OS X: with ARC, Grand ...
iOS framework file was built for x86_64 which is not the architecture being linked (arm64), linker command failed with exit code 1
Friday Q&A 2013-09-27: ARM64 and You
深入解析 ObjC 中方法的結(jié)構(gòu)
What is a meta-class in Objective-C?
Classes and metaclasses
用 isa 承載對象的類信息

最后編輯于
?著作權(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)容

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