iOS探索 isa初始化&指向分析

歡迎閱讀iOS探索系列(按序閱讀食用效果更加)html

寫在前面

在介紹isa以前,先介紹一個位域和結構體的知識點bash

1、位域

1.定義

有些信息在存儲時,並不須要佔用一個完整的字節,而只需佔幾個或一個二進制位。例如在存放一個開關量時,只有0和1兩種狀態,用1位二進位便可。爲了節省存儲空間並使處理簡便,C語言提供了一種數據結構,稱爲位域位段數據結構

所謂位域就是把一個字節中的二進位劃分爲幾個不一樣的區域,並說明每一個區域的位數。每一個域有一個域名,容許在程序中按域名進行操做——這樣就能夠把幾個不一樣的對象用一個字節的二進制位域來表示架構

2.與結構體比較

位域的使用與結構體相仿,它自己也是結構體的一種ide

// 結構體
struct FXStruct {
    // (類型說明符 元素);
    char a;
    int b;
} FXStr;

// 位域
struct FXBitArea {
    // (類型說明符 位域名: 位域長度);
    char a: 1;
    int b: 3;
} FXBit;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"Struct:%lu——BitArea:%lu", sizeof(FXStr), sizeof(FXBit));
    }
    return 0;
}
複製代碼

輸出Struct:8——BitArea:4函數

位域有興趣的能夠看下struct中位域的定義post

2、聯合體

1.定義

當多個數據須要共享內存或者多個數據每次只取其一時,能夠利用聯合體(union)性能

  • 聯合體是一個結構
  • 它的全部成員相對於基地址的偏移量都爲0
  • 此結構空間要大到足夠容納最"寬"的成員
  • 各變量是「互斥」的——共用一個內存首地址,聯合變量可被賦予任一成員值,但每次只能賦一種值, 賦入新值則衝去舊值

2.與結構體比較

結構體每一個成員依次存儲,聯合體中全部成員的偏移地址都是0,也就是全部成員是疊在一塊兒的,因此在聯合體中在某一時刻,只有一個成員有效——結構體內存大小取決於全部元素,聯合體取決於最大那個 優化

3、isa結構/流程分析

1.isa初始化

在以前的iOS探索 alloc流程中輕描淡寫的提了一句obj->initInstanceIsa(cls, hasCxxDtor)——只知道內部調用initIsa(cls, true, hasCxxDtor)初始化isa,並無對isa進行細說ui

2.initIsa分析

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

#if SUPPORT_INDEXED_ISA
        assert(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        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;
#endif

        // This write must be performed in a single store in some cases
        // (for example when realizing a class because other threads
        // may simultaneously try to use the class).
        // fixme use atomics here to guarantee single-store and to
        // guarantee memory order w.r.t. the class index table
        // ...but not too atomic because we don't want to hurt instantiation
        isa = newisa;
    }
}
複製代碼

①建立對象跟着斷點不難發現nonpointer爲true

if-else跳轉了else流程——SUPPORT_INDEXED_ISA表示isa_t中存放的 Class信息是Class 的地址,仍是一個索引(根據該索引可在類信息表中查找該類結構地址)

isa_t newisa(0)至關於初始化isa這個東西,new.至關於給isa賦值屬性

1.SUPPORT_INDEXED_ISA適用於WatchOS 2.isa做爲聯合體具備互斥性,而cls、bits是isa的元素,因此當!nonpointer=true時對cls進行賦值操做,爲false是對bits進行賦值操做(反正都是一家人,共用一塊內存地址)

3.isa結構

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

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};
複製代碼

①首先isa是個聯合體,擁有兩個初始化方法

isa內部有個Class cls——Classisa有綁定關係——isa指向類的結構

③isa採用聯合體+位域的形式來優化內存(ISA_BITFIELD是個位域宏定義)

先初始化bits決定聯合體的長度,再對聯合體內的位域ISA_BITFIELD進行賦值

聯合體全部屬性共用內存,內存長度等於其最長成員的長度,使代碼存儲數據高效率的同時,有較強的可讀性;而位域能夠容納更多類型

4.ISA_BITFIELD宏定義

#if SUPPORT_PACKED_ISA

    // extra_rc must be the MSB-most field (so it matches carry/overflow flags)
    // nonpointer must be the LSB (fixme or get rid of it)
    // shiftcls must occupy the same bits that a real class pointer would
    // bits + RC_ONE is equivalent to extra_rc + 1
    // RC_HALF is the high bit of extra_rc (i.e. half of its range)

    // future expansion:
    // uintptr_t fast_rr : 1; // no r/r overrides
    // uintptr_t lock : 2; // lock for atomic property, @synch
    // uintptr_t extraBytes : 1; // allocated with extra bytes

# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD \ 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)

# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_BITFIELD \ uintptr_t nonpointer : 1; \ uintptr_t has_assoc : 1; \ uintptr_t has_cxx_dtor : 1; \ uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \ uintptr_t magic : 6; \ uintptr_t weakly_referenced : 1; \ uintptr_t deallocating : 1; \ uintptr_t has_sidetable_rc : 1; \ uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)

# else
# error unknown architecture for packed isa
# endif

// SUPPORT_PACKED_ISA
#endif
複製代碼

不一樣架構下isa所佔內存均爲8字節——64位,但內部分佈有所不一樣,arm64架構isa內部成員分佈以下圖

nonpointer:表示是否對isa指針開啓指針優化——0:純isa指針;1:不止是類對象地址,isa 中包含了類信息、對象的引用計數等

has_assoc:關聯對象標誌位,0沒有,1存在

has_cxx_dtor:該對象是否有 C++ 或者 Objc 的析構器,若是有析構函數,則須要作析構邏輯, 若是沒有,則能夠更快的釋放對象

shiftcls:存儲類指針的值,在開啓指針優化的狀況下,在 arm64 架構中有 33 位用來存儲類指針

magic:用於調試器判斷當前對象是真的對象仍是沒有初始化的空間

weakly_referenced:對象是否被指向或者曾經指向一個 ARC 的弱變量, 沒有弱引用的對象能夠更快釋放

deallocating:標誌對象是否正在釋放內存

has_sidetable_rc:當對象引用技術大於 10 時,則須要借用該變量存儲進位

extra_rc:當表示該對象的引用計數值,其實是引用計數值減 1, 例如,若是對象的引用計數爲 10,那麼 extra_rc 爲 9。若是引用計數大於 10, 則須要使用到下面的 has_sidetable_rc

shiftcls以外瞭解便可

5.shiftcls關聯類

@interface FXPerson : NSObject
@end
@implementation FXPerson
@end int main(int argc, const char * argv[]) {
    @autoreleasepool {
        FXPerson *p = [[FXPerson alloc] init];
        NSLog(@"%@",p);
    }
    return 0;
}
複製代碼

父類NSObject結構中能夠有個isa屬性

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
複製代碼

可是內存中屬性的位置是會由於優化發生改變的,下面就來證明下內存中第一位必定是isa

5.1 反證法(不推薦)

假設實例對象第一位內存是isa

①打印第一位內存

②二進制打印第一位內存的內存值

③由於模擬器是x86架構的,由isa位域結構可知,shiftcls前面有3位——右移3位——抹去isa前3位

shiftcls後面有17位——左移17位——抹去isa後17位

⑤由於末尾的0都是咱們添加的——右移17位——獲得shiftcls

⑥根據newisa.shiftcls = (uintptr_t)cls >> 3;——shiftcls等於class地址右移3位

比對兩組shiftcls二進制,發現它們二進制同樣(前面不同是由於沒抹掉)

若是你聽得雲裏霧裏的話,請看第二種方法

5.2 經過object_getClass(推薦)

①在<objc/runtime.h>下使用object_getClass方法

#import <objc/runtime.h>

@interface FXPerson : NSObject
@end
@implementation FXPerson
@end int main(int argc, const char * argv[]) {
    @autoreleasepool {
        FXPerson *p = [[FXPerson alloc] init];
        object_getClass(p);
    }
    return 0;
}
複製代碼

②跟進object_getClass方法

/*********************************************************************** * object_getClass. * Locking: None. If you add locking, tell gdb (rdar://7516456). **********************************************************************/
Class object_getClass(id obj) {
    if (obj) return obj->getIsa();
    else return Nil;
}
複製代碼

③跟進getIsa()

#if SUPPORT_TAGGED_POINTERS

inline Class 
objc_object::getIsa() 
{
    if (!isTaggedPointer()) return ISA();

    uintptr_t ptr = (uintptr_t)this;
    if (isExtTaggedPointer()) {
        uintptr_t slot = 
            (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
        return objc_tag_ext_classes[slot];
    } else {
        uintptr_t slot = 
            (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
        return objc_tag_classes[slot];
    }
}
複製代碼

④通常isTaggedPointer都爲false,跟進ISA()

#if SUPPORT_NONPOINTER_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
}
複製代碼

⑤已知SUPPORT_INDEXED_ISA適用於WatchOS,那麼走return (Class)(isa.bits & ISA_MASK);

# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# endif
複製代碼

⑥檢驗

打印出isa & mask的值,與class相比較(mask取x86架構)

從上述兩種方法都能得出實例對象首地址必定 是isa

6.isa初始化流程圖

4、isa走位

1.類在內存中只會存在一份

@interface FXPerson : NSObject
@end
@implementation FXPerson
@end int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Class class1 = [FXPerson class];
        Class class2 = [FXPerson alloc].class;
        Class class3 = object_getClass([FXPerson alloc]);
        Class class4 = [FXPerson alloc].class;
        NSLog(@"\n%p\n%p\n%p\n%p",class1,class2,class3,class4);
    }
    return 0;
}
複製代碼
0x1000024a0
0x1000024a0
0x1000024a0
0x1000024a0
複製代碼

輸出證實類在內存中只會存在一個,而實例對象能夠存在多個(自行證實)

2.1 經過對象/類查看isa走向

其實和實例對象同樣,都是由上級實例化出來的——類的上級叫作元類

咱們先用p/x打印類的內存地址,再用x/4gx打印內存結構取到對應的isa,再用mask進行偏移獲得isa指向的上級(等同於object_getClass)依次循環

①打印 FXPerson類取得isa

②由FXPerson類進行偏移獲得FXPerson元類指針,打印FXPerson元類取得isa

③由FXPerson元類進行偏移獲得NSObject根元類指針,打印NSObject根元類取得isa

④由NSObject根元類進行偏移獲得NSObject根元類自己指針

⑤打印NSObject根類取得isa

⑥由NSObject根類進行偏移獲得NSObject根元類指針

結論:

實例對象-> 類對象 -> 元類 -> 根元類 -> 根元類(自己)

NSObject(根類) -> 根元類 -> 根元類(自己)

指向根元類的isa都是同樣的

2.2.經過NSObject查看isa走向

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // NSObject實例對象
        NSObject *object1 = [NSObject alloc];
        // NSObject類
        Class class = object_getClass(object1);
        // NSObject元類
        Class metaClass = object_getClass(class);
        // NSObject根元類
        Class rootMetaClass = object_getClass(metaClass);
        // NSObject根根元類
        Class rootRootMetaClass = object_getClass(rootMetaClass);
        NSLog(@"\n%p 實例對象\n%p 類\n%p 元類\n%p 根元類\n%p 根根元類",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
    }
    return 0;
}
複製代碼
0x100660ba0 實例對象
0x7fffacd3d140 類
0x7fffacd3d0f0 元類
0x7fffacd3d0f0 根元類
0x7fffacd3d0f0 根根元類
複製代碼

由於是NSObject(根類)它的元類就是根元類——輸出可得根元類指向本身

2.3 證實類、元類是系統建立的

①運行時僞證法

main以前 FXPerson類FXPerson元類已經存在在內存中,不過此時程序已經在運行了,並無什麼說服力

②查看MachO文件法

FXPerson已經存在在MachO文件中

結論:
對象是程序猿根據類實例化的
類是代碼編寫的,內存中只有一份,是系統建立的
元類是系統編譯時,系統編譯器建立的,便於方法的編譯
複製代碼

3.isa走位圖

isa走位(虛線): 實例對象-> 類對象 -> 元類 -> 根元類 -> 根元類(自己)

繼承關係(實線):NSObject父類爲nil,根元類的父類爲NSObject

小彩蛋——編譯器優化

  • None[-O0]: 不優化。在這種設置下, 編譯器的目標是下降編譯消耗,保證調試時輸出指望的結果。程序的語句之間是獨立的:若是在程序的停在某一行的斷點出,咱們能夠給任何變量賦新值抑或是將程序計數器指向方法中的任何一個語句,而且能獲得一個和源碼徹底一致的運行結果。
  • Fast[-O1]: 大函數所需的編譯時間和內存消耗都會稍微增長。在這種設置下,編譯器會嘗試減少代碼文件的大小,減小執行時間,但並不執行須要大量編譯時間的優化。在蘋果的編譯器中,在優化過程當中,嚴格別名,塊重排和塊間的調度都會被默認禁止掉。
  • Faster[-O2]: 編譯器執行全部不涉及時間空間交換的全部的支持的優化選項。在這種設置下,編譯器不會進行循環展開、函數內聯或寄存器重命名。和'Fast[-O1]'項相比,此設置會增長編譯時間和生成代碼的性能。
  • Fastest[-O3]: 在開啓'Fast[-O1]'項支持的全部優化項的同時,開啓函數內聯和寄存器重命名選項。這個設置有可能會致使二進制文件變大。
  • Fastest, Smallest[-Os]: 優化大小。這個設置開啓了'Fast[-O1]'項中的全部不增長代碼大小的優化選項,並會進一步的執行能夠減少代碼大小的優化。
  • Fastest, Aggressive Optimizations[-Ofast]: 這個設置開啓了'Fastest[-O3]'中的全部優化選項,同時也開啓了可能會打破嚴格編譯標準的積極優化,但並不會影響運行良好的代碼。

Debug默認不優化,Relese默認Fastest, Smallest[-Os]

相關文章
相關標籤/搜索