在前一篇文章中,我們已經(jīng)探討了iOS底層原理--isa與類關(guān)聯(lián)的原理,isa包含了Class類,從而將isa與Class類進(jìn)行了關(guān)聯(lián)。
那么,我們現(xiàn)在來(lái)看下類的結(jié)構(gòu)分析。
首先我們還是看一段代碼
//
// main.m
// KCObjc
//
// Created by Cooci on 2020/7/24.
//
#import <Foundation/Foundation.h>
#import "LGPerson.h"
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
// objc_alloc VS alloc
// NSObject LGPerson
// NSObject 根類
// sel 處理 llvm
// define ISA_MASK 0x00007ffffffffff8ULL
NSObject *objc1 = [NSObject alloc];
LGPerson *objc2 = [LGPerson alloc];
// LGPerson *objc3 = [LGPerson alloc];
NSLog(@"Hello, World! %@",objc2);
}
return 0;
}
- 我們將斷點(diǎn)打到
objc2處,然后查看objc2的內(nèi)存情況。
image.png
x/4gx表示查看objc2的內(nèi)存情況
- 通過上面的輸出,我們看到了內(nèi)存,然后我們知道,內(nèi)存中第一位代表了對(duì)象的
isa指針的地址,所以0x001d800100002205這個(gè)表示isa指針的地址。 - 通過iOS底層原理--isa與類關(guān)聯(lián)的原理這篇文章,我們將
isa于ISA_MASK進(jìn)行與運(yùn)算可以得的isa指針指向的類的地址,我們進(jìn)行驗(yàn)證。
ISA_MASK在x86_64中的地址0x00007ffffffffff8ULL

通過上面的圖片我們可以看到,經(jīng)過與運(yùn)算后,我們得出了最后得到的地址中存放了
LGPerson,至此我們可以得到結(jié)論。對(duì)象的isa指針指向了類。-
然后我們?cè)賮?lái)一步,將類的
isa拿出來(lái)和ISA_MASK進(jìn)行與運(yùn)算,得到什么呢?我們驗(yàn)證一下。
LGPerson.png 看到輸出結(jié)果,顯示還是
LGPerson,為什么呢?這就是所謂的元類。
元類(Meta Class)
我們都知道
對(duì)象的isa是指向類,類的其實(shí)也是一個(gè)對(duì)象,可以稱為類對(duì)象,其isa的位域指向蘋果定義的元類元類是系統(tǒng)給的,其定義和創(chuàng)建都是由編譯器自動(dòng)完成,在這個(gè)過程中,類的歸屬來(lái)自于元類元類
是類對(duì)象的類,每個(gè)類都有一個(gè)獨(dú)一無(wú)二的元類用來(lái)存儲(chǔ) 類方法的相關(guān)信息。元類本身是沒有名稱的,由于與類相關(guān)聯(lián),所以使用了同類名一樣的名稱
上面這段話摘自Style_月月的文章iOS-底層原理 08:類 & 類結(jié)構(gòu)分析,大家也可以去看看她的其他文章,都寫的非常詳細(xì)。
那么我們接著按照上面的流程進(jìn)行探索
-
首先我們得到
LGPerson的元類
元類 -
此事可以看到,在
LGPerson的元類中,我們?nèi)匀豢梢阅玫?code>isa指針。那么我們用isa于ISA_MASK進(jìn)行與運(yùn)算,得到什么呢?請(qǐng)往下看。
NSObject.png
可以看到,經(jīng)過一系列的操作,LGPerson的元類最終竟然指向了NSObject。
而且NSObject的首地址和isa指針的地址相同,所以也可以說(shuō)明NSObject指向的是自己。
- 那我們看看
NSObject的情況,

可以看到,這次打印的地址跟我們之前得到的NSObject地址不一樣,難道NSObject在內(nèi)存中有多份嗎?那我們?cè)俅蛴∫幌峦暾牡刂贰?br>

再和之前的地址對(duì)比一下,可以看到一樣。
所以得出結(jié)論,LGPerson元類的isa指針并不是指向NSObject,而是指向NSObject的元類,即根元類,而NSObject也是指向根元類,而根元類最終指向自己。

上面就是isa和繼承關(guān)系的走勢(shì)圖,總結(jié)如下:
isa
- 實(shí)例對(duì)象
isa指向類對(duì)象 - 類對(duì)象
isa指向元類 - 元類
isa指根(NSObject)元類 - 根(NSObject)元類指向自己
superclass(繼承關(guān)系)
- 子類繼承自父類
- 類才有繼承關(guān)系,而實(shí)例對(duì)象不存在繼承關(guān)系
- 所有類都繼承
NSObject,而NSObject繼承自nil -
NSObject的元類也繼承自NSObject
objc_object & objc_class
在我們之前的分析中,通過clang進(jìn)行解析
#ifndef _REWRITER_typedef_LGPerson
#define _REWRITER_typedef_LGPerson
typedef struct objc_object LGPerson;
typedef struct {} _objc_exc_LGPerson;
#endif
extern "C" unsigned long OBJC_IVAR_$_LGPerson$_name;
struct LGPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS; //isa
NSString *_name;
};
可以看到LGPerson是objc_object類型的 ,LGPerson的底層編譯LGPerson_IMPL中又包含了NSObject_IMPL,NSObject_IMPL是一個(gè)結(jié)構(gòu)體,包含了Class isa
struct NSObject_IMPL {
Class isa;
};
Class又是一個(gè)objc_class類型的結(jié)構(gòu)提體。
typedef struct objc_class *Class;
所以,objc_object就是一個(gè)根類,而objc_class定義了一個(gè)Class,這證明了每個(gè)對(duì)象都有isa指針。
而從我們的源碼,可以看出來(lái)objc_class繼承自objc_object

內(nèi)存偏移
在接著往下分析之前,我們來(lái)了解一個(gè)知識(shí) -- 內(nèi)存偏移。
普通指針
先看下面的案例,打印a、b的值和地址。
int a = 10;
int b = 10;
//打印地址和
NSLog(@"%d --- %p",a,&a);
NSLog(@"%d --- %p",b,&b);
如下圖所示

a、b都指向10,但是a、b的地址不一樣,這是一種拷貝,屬于
值拷貝,也稱為淺拷貝。a,b的地址之間相差 4 個(gè)字節(jié),這取決于a、b的類型
對(duì)象指針
LGPerson *p1 = [LGPerson alloc];
LGPerson *p2 = [LGPerson alloc];
NSLog(@"%@ --- %p",p1,&p1);
NSLog(@"%@ --- %p",p2,&p2);
打印結(jié)果如下所示:

- p1、p2 是指針,p1 是 指向
[LGPerson alloc]創(chuàng)建的空間地址,即內(nèi)存地址,p2 同理
&p1、&p2是指向 p1、p2對(duì)象指針的地址,這個(gè)指針 就是二級(jí)指針
數(shù)組指針
//數(shù)組指針
int c[4] = {1, 2, 3, 4};
int *d = c;
NSLog(@"%p -- %p - %p", &c, &c[0], &c[1]);
NSLog(@"%p -- %p - %p", d, d+1, d+2);
打印結(jié)果如下:

-
&c和&c[0]都是取 首地址,即數(shù)組名等于首地址 -
&c與&c[1]相差4個(gè)字節(jié),地址之間相差的字節(jié)數(shù),主要取決于存儲(chǔ)的數(shù)據(jù)類型 - 可以通過 首地址+偏移量取出數(shù)組中的其他元素,其中偏移量是數(shù)組的下標(biāo),內(nèi)存中首地址實(shí)際移動(dòng)的字節(jié)數(shù) 等于
偏移量 * 數(shù)據(jù)類型字節(jié)數(shù)
類結(jié)構(gòu)
我們已經(jīng)知道,類在底層最終會(huì)轉(zhuǎn)化成objc_class,objc_class結(jié)構(gòu)如下所示,

而
bits有我們需要的所有信息,那么要拿到bits,我們需要進(jìn)行內(nèi)存偏移。那么看看
bits前面的東西有多大,進(jìn)行多少偏移。
-
isa屬性:繼承自objc_object的isa,占 8字節(jié) -
superclass屬性:Class類型,Class是由objc_object定義的,是一個(gè)指針,占8字節(jié) -
cache,是一個(gè)cache_t類型的結(jié)構(gòu)體,所以我們來(lái)分析下cache
cache_t
我們來(lái)看看cache_t的結(jié)構(gòu),進(jìn)入cache類cache_t的定義(只貼出了結(jié)構(gòu)體中非static修飾的屬性,主要是因?yàn)閟tatic類型的屬性 不存在結(jié)構(gòu)體的內(nèi)存中),有如下幾個(gè)屬性
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets; // 是一個(gè)結(jié)構(gòu)體指針類型,占8字節(jié)
explicit_atomic<mask_t> _mask; //是mask_t 類型,而 mask_t 是 unsigned int 的別名,占4字節(jié)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets; //是指針,占8字節(jié)
mask_t _mask_unused; //是mask_t 類型,而 mask_t 是 uint32_t 類型定義的別名,占4字節(jié)
#if __LP64__
uint16_t _flags; //是uint16_t類型,uint16_t是 unsigned short 的別名,占 2個(gè)字節(jié)
#endif
uint16_t _occupied; //是uint16_t類型,uint16_t是 unsigned short 的別名,占 2個(gè)字節(jié)
-
【情況一】if流程
buckets類型是struct bucket_t *,是結(jié)構(gòu)體指針類型,占8字節(jié)mask是mask_t類型,而mask_t是unsigned int的別名,占4字節(jié)
-
【情況二】elseif流程
_maskAndBuckets是uintptr_t類型,它是一個(gè)指針,占8字節(jié)_mask_unused是mask_t類型,而mask_t是uint32_t類型定義的別名,占4```字節(jié)_flags是uint16_t類型,uint16_t是unsigned short的別名,占2個(gè)字節(jié)_occupied是uint16_t類型,uint16_t是 unsigned short的別名,占2```個(gè)字節(jié)
總結(jié):所以最后計(jì)算出cache類的內(nèi)存大小 = 12 + 2 + 2 = 16字節(jié)
所以最終要拿到bits需要平移32位。
class_data_bits_t
- 首先打印出
LGPerson
LGPerson.png - 拿出首地址
0x100002200平移32位 得到0x100002220,得到bits
bits .png - 獲取
data()
data() .png - 獲取
class_rw_t
class_rw_t .png - 獲取屬性列表
properties()
properties .png - 我們可以看到里面是一個(gè)
list,獲取其中的元素
image.png
可以看到list中的屬性與我們LGPerson中的屬性一致。









