iOS底層原理_03:OC對(duì)象原理(下)

第三節(jié)課 OC對(duì)象原理(下)

全篇開始之前我們想一個(gè)問題,研究了這么久對(duì)象,究竟什么是對(duì)象呢??

對(duì)象本質(zhì)以及拓展

Clang

探索對(duì)象的本質(zhì)前,我們先了解一個(gè)編譯器:clang
Clang是一個(gè)C語言、C++、OC語言的輕量級(jí)編譯器。源代碼發(fā)布于BSD協(xié)議下。Clang將支持其普通lambda表達(dá)式、返回類型的簡化處理以及更好的處理constexpr關(guān)鍵字。

clang是一個(gè)由Apple主導(dǎo)編寫,基于LLVM的C/C++/OC的編譯器

探索本質(zhì)

1、在main中自定義一個(gè)類LGPerson,有一個(gè)屬性name

@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end

@implementation LGPerson
@end

2、通過終端,利用clangmain.m編譯成 main.cpp

clang -rewrite-objc main.m -o main.cpp

3、打開編譯好的main.cpp,找到LGPerson的定義,發(fā)現(xiàn)LGPerson在底層會(huì)被編譯成struct 結(jié)構(gòu)體

03-對(duì)象的本質(zhì)是結(jié)構(gòu)體.png

我們可以看到,這是一個(gè)結(jié)構(gòu)體,里面嵌套了一個(gè)結(jié)構(gòu)體,結(jié)構(gòu)體能夠繼承嘛?其實(shí)是可以的,但是是屬于偽繼承,偽繼承的方式是直接將NSObject結(jié)構(gòu)體定義為LGPerson中的第一個(gè)屬性,意味著LGPerson擁有NSObject中的所有成員變量。

LGPerson_IMPL中的第一個(gè)屬性,其實(shí)就是isa,是繼承自NSObject,

03-IVARS.png

通過上圖我們可以看到成員變量是Class isa。通常叫isa叫做isa指針,那么這里的Class應(yīng)該是個(gè)指針類型,在main.cpp文件中全局搜索*Class。代碼如下

03-class.png

LGPerson的類型是objc_object,在OC層面 我們的LGPerson是繼承NSObject,其實(shí)在下層真正的實(shí)現(xiàn)就是objc_object。

03-objc_object.png

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

在剛才看class的過程中,還發(fā)現(xiàn)了兩個(gè)東西


03-id、sel.png

熟悉的idSEL。常用的id原來是一個(gè)objc_object結(jié)構(gòu)體指針,這就解釋了id修飾變量和作為返回值的時(shí)候?yàn)槭裁床患?code>*,下面的SEL也是結(jié)構(gòu)體指針這個(gè)也是之前不知道的呢,就稍微了解下吧~

在稍下面一點(diǎn)我們看到一些奇奇怪怪的一些東西

03-方法的get、set.png

這其實(shí)就是我們的Get方法Set方法,但是參數(shù)部分呢?我們可在OC中沒有看到,這其實(shí)就 是我們的隱藏參數(shù)。
這個(gè)Get方法中的參數(shù)self+OBJC_IVAR_$_LGPerson$_name這一步就是我們之前講過的,要獲取一個(gè)類的對(duì)象的地址,是通過獲取類的首地址+對(duì)象的偏移量的方法來最終取得對(duì)象的。

小結(jié)

所以從上述探索過程中可以得出:

  • OC對(duì)象的本質(zhì)其實(shí)就是結(jié)構(gòu)體
  • LGPerson中的isa繼承NSObject中的isa

聯(lián)合體位域拓展補(bǔ)充

位域(位段)

位域:在C語言中允許在一個(gè)結(jié)構(gòu)體中以位為單位來指定其成員所占內(nèi)存長度,這種以位為單元的成員稱為位域。

struct HZMCar1 {
    BOOL front; // 0 1
    BOOL back;
    BOOL left;
    BOOL right;
};
struct HZMCar1 car1;
NSLog(@"%ld",sizeof(car1));
<--打印輸出-->
4

在開發(fā)當(dāng)中遇到這種情況,其實(shí)BOOL就只有兩種情況,但是卻使用了4個(gè)字節(jié)32位,其實(shí)我們只用4位就可以了,這樣我們就只使用了1字節(jié),還有3字節(jié)其實(shí)是浪費(fèi)的。接下來我們進(jìn)行改進(jìn)一下

struct HZMCar2 {
    BOOL front: 1;
    BOOL back : 1;
    BOOL left : 1;
    BOOL right: 1;
};
struct HZMCar2 car2;
NSLog(@"%ld",sizeof(car2));
<--打印輸出-->
1

其實(shí)就是指定對(duì)象所占內(nèi)存長度

聯(lián)合體(共用體)

struct LGStudent {
    char        *name;
    int         age;
    double      height ;
};

union LGStudent2 {
    char        *name;
    int         age;
    double      height ;
};
struct LGStudent   student;
student.name = "HZM";
student.age  = 18;

union LGStudent2    student2;
student2.name = "HZM";
student2.age  = 18;
NSLog(@"%ld-%ld",sizeof(student),sizeof(student2));
<--打印輸出-->
24-8

首先我們能觀察到,同樣的成員變量,內(nèi)存大小天差地別,這是為什么呢?我們來通過斷點(diǎn)查看下。

03-聯(lián)合體.png

可以看到聯(lián)合的成員變量在未賦值的情況下是一塊臟數(shù)據(jù)、臟內(nèi)存,當(dāng)?shù)诙€(gè)變量被賦值的時(shí)候又將第一個(gè)變量清理了,所以聯(lián)合體的各種變量互斥,每次只能賦值一個(gè),這樣就可以接受大幅空間,這也就是我們之前看到的為什么聯(lián)合體的內(nèi)存大小比較小的原因。

小結(jié):

位域和聯(lián)合體對(duì)比
位域:優(yōu)點(diǎn)是所有變量可以共存,缺點(diǎn)是內(nèi)存空間粗放,不管用不用,都分配
聯(lián)合體:優(yōu)點(diǎn)是內(nèi)存更精細(xì)靈活,節(jié)省空間,缺點(diǎn)是各種變量互斥

isa的類型 isa_t

以下是isa指針的類型isa_t的定義,從定義中可以看出是通過聯(lián)合體(union)定義的。

union isa_t { //聯(lián)合體
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    //提供了cls 和 bits ,兩者是互斥關(guān)系
    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};  

isa_t類型使用聯(lián)合體的原因也是基于內(nèi)存優(yōu)化的考慮,這里的內(nèi)存優(yōu)化是指在isa指針中通過char + 位域(即二進(jìn)制中每一位均可表示不同的信息)的原理實(shí)現(xiàn)。通常來說,isa指針占用的內(nèi)存大小是8字節(jié),即64位,已經(jīng)足夠存儲(chǔ)很多的信息了,這樣可以極大的節(jié)省內(nèi)存,以提高性能
從isa_t的定義中可以看出:

  • 提供了兩個(gè)成員,clsbits,由聯(lián)合體的定義所知,這兩個(gè)成員是互斥的,也就意味著,當(dāng)初始化isa指針時(shí),只有一個(gè)變量有值

    • 通過cls初始化,bits無默認(rèn)值
    • 通過bits初始化,cls有默認(rèn)值
  • 還提供了一個(gè)結(jié)構(gòu)體定義的位域,用于存儲(chǔ)類信息及其他信息,結(jié)構(gòu)體的成員ISA_BITFIELD,這是一個(gè)宏定義,有兩個(gè)版本 __arm64__(對(duì)應(yīng)ios 移動(dòng)端) 和 __x86_64__(對(duì)應(yīng)macOS),以下是它們的一些宏定義,如下圖

    03-ISA_BITFIELD.png

nonpointer:表示是否對(duì)isa指針開啟指針優(yōu)化
0:純isa指針,1:不止是類對(duì)象地址,isa中包含了類信息、對(duì)象的引用計(jì)數(shù)等
has_assoc:關(guān)聯(lián)對(duì)象標(biāo)志位
0:沒有,1:存在
has_cxx_dtor:該對(duì)象是否有C++或者Objc的析構(gòu)器,如果有析構(gòu)函數(shù),則需要做析構(gòu)邏輯,如果沒有,則可以更快的釋放對(duì)象
shiftcls:開啟指針優(yōu)化的情況下,在arm64架構(gòu)中有33位用來存儲(chǔ)類指針,x86_64中占 44
magic:用于調(diào)試器判斷當(dāng)前對(duì)象是真的對(duì)象還是沒有初始化的空間
weakly_referenced:標(biāo)志對(duì)象是否被指向或者曾經(jīng)指向一個(gè)ARC的弱變量
沒有弱引用的對(duì)象可以更快釋放。
deallocating:標(biāo)志對(duì)象是否正在釋放內(nèi)存
has_sidetable_rc:當(dāng)對(duì)象引用計(jì)數(shù)大于 10 時(shí),則需要借用該變量存儲(chǔ)進(jìn)位
extra_rc:當(dāng)表示該對(duì)象的引用計(jì)數(shù)值,實(shí)際上是引用計(jì)數(shù)值減 1
例如,如果對(duì)象的引用計(jì)數(shù)為 10,那么 extra_rc9。如果引用計(jì)數(shù)大于 10, 則需要使用到上面的 has_sidetable_rc。

03-isa存儲(chǔ)情況.png

ISA_MASK

ISA_MASK是一個(gè)宏,一個(gè)掩碼。__x86_64__ 的值等于 0x00007ffffffffff8ULL__arm64__ 的值等于0x0000000ffffffff8ULL。通過x/4g p獲取到的首位地址就是isa的值是0x001d800100008275,驗(yàn)證下0x001d800100008275&0x00007ffffffffff8ULL結(jié)果

03-MASK.png

對(duì)象通過 isa & 掩碼 得到類的信息

isa的位運(yùn)算

通過上面的學(xué)習(xí)我們已經(jīng)知道了isashiftcls是用來存儲(chǔ)類指針,所以我們降妖獲取類指針其實(shí)也可以通過位運(yùn)算獲取shiftcls的最終值。

03-位運(yùn)算.png

通過上圖我們發(fā)現(xiàn),其實(shí)我們要獲取的shiftcls正好在中間,左邊有3位,右邊有28位,那我們?nèi)绻@取的話就可以將shiftcls整體向左移3位,將多余部分?jǐn)D出去,向右移20位,將左邊的多余部分?jǐn)D出去,最后左移17位恢復(fù)原位,這個(gè)時(shí)候剩下的不就是我們需要的shiftcls了。我們來驗(yàn)證下

03-驗(yàn)證位運(yùn)算.png

可以看到位運(yùn)算結(jié)束后的結(jié)果與我們輸出的class相同。

最后編輯于
?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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