Object-C的底層都是通過C/C++來實現的,所以OC中的對象也會轉化成C/C++中的某一個數據結構,

我們在終端里通過指令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main_arm64.cpp
將oc代碼轉化為c++代碼,我們可以看到NSObject的底層結構是:
struct NSObject_IMPL { Class isa; };
Class是一個指向對象的結構體指針
typedef struct objc_class *Class;
所以NSObject最終會轉化成一個結構體,內部只有一個指向對象的結構體指針
所以NSObject對象只會使用8個字節的內存空間來存儲指針(當然 實際上給它分配了16個內存空間)
NSLog(@"%zd",class_getInstanceSize([NSObject class])); //實例對象的成員所占用的大小8 (實際使用的) NSLog(@"%zd",malloc_size((__bridge const void *)(obj))); //整個結構體占用的是16(實際分配的)
同時,通過閱讀源碼我們得知,當創建的對象分配的內存空間小於16個字節的時候 系統都會分配16個字節的空間 這屬於是蘋果規定。
size_t instanceSize(size_t extraBytes) { size_t size = alignedInstanceSize() + extraBytes; // CF requires all objects be at least 16 bytes. if (size < 16) size = 16; return size; }
如果有一個student類繼承了object並且有倆個int屬性,那么student所占用的內存是多少呢?

student實際占用內存為16字節,系統分配的內存也是16字節。
假設有個person繼承NSObject,student繼承person,那么person和student各占用多少內存呢?

最終通過打印我們發現,person,實際占用16,系統分配16,student實際占用16,系統分配16.
為什么?person實際占用16??int 4個字節 isa 8個字節 應該是12個字節啊?這就涉及到了前面寫到的結構體內存對齊了。
對象的分類
oc中的對象主要可以分為三類:

1.實例對象,就是通過類alloc出來的對象,每次調用alloc都會生成一個新的實例對象

object1 和 object2 就是兩個實例對象
實例對象在內存中存儲的信息包括:
1、isa指針 (其實isa也算是對象的成員變量 也就是說實例對象內部只包含自己的成員變量)
2、其他成員變量(這里是存儲成員變量的具體值)

2、類對象(class)

objectClass1-5 都是NSObject的類對象 ,因為每個類在內存中有且只有一個類對象 所以上面五個類對象其實是同一個對象
類對象在內存中存儲的信息主要有:
1、isa指針
2、superclass指針
3、成員變量(這里的成員變量只是描述性的 比如有哪些變量 是什么類型的 並不是實例對象的具體變量值)
4、類的對象方法(-號開頭的方法)
5、類的協議信息和屬性信息

類對象的本質結構↓↓↓

3、元類對象(meta-class)
![]()
objectMetaClass就是NSObject的元類對象,元類對象也是每個類在內存中有且只有一個,元類對象和類對象在結構上非常相似。
元類對象在內存中春初的主要信息有:
1、isa指針
2、superclass指針
3、類方法(+號開頭的方法)

我們看到通過object_getclass方法即能獲得元類對象 也能獲得類對象 通過查看源碼我們可以得知object_getclass會判斷傳進來的參數是類對象還是實例對象 如果是實例對象則返回類對象 如果傳進來的是類對象則返回元類對象
我們也可以通過下面的函數來判斷對象是不是元類對象

也就是說通過alloc創建的是實例對象 通過object_getclass(類對象)創建的是元類對象 其他對象則是類對象 但是類對象和元類對象有且只有一個
三類對象中 都含有isa指針,那么這個isa指針指向什么?
實例對象的isa指向類對象 類對象的isa指向元類對象 元類對象的isa指向基類的元類對象
正是通過isa指針 才讓三種對象產生關聯
比如說,一個實例對象想調用對象方法 但是對象方法存放在類對象中 那么就是通過isa找到對象方法再進行調用
同理 當調用類方法的時候 類方法是存放在元類對象中的 類對象通過isa指針找到元類對象 讀取類方法列表中的類方法進行調用

superclass指針
在類對象和元類對象中都有一個superclass指針,其實這兩種對象中的superclass指針作用類似,都是指向父類對象
類對象中的superclass指針:
比如現在有一個Person對象繼承自NSObject 有一個Student繼承自Person 當studen的實例對象調用對象方法的時候,首先實例對象會根據自己的isa指針去類對象中找有沒有對應的方法 沒有的話類對象會根據自己的superclass指針去父類的類對象中去查找(也就是student的類對象根據superclass指針去Person的類對象中去查找有沒有對應的對象方法 再沒有的話Person的類對象會根據自己的superclass指針去NSObject的類對象中去尋找 尋找到基類在沒有對應方法的話就會報方法找不到的錯誤)

而元類對象中的superclass指針也是指引類對象去父類對象中尋找對應的類方法:
按照上面的例子,Student這個類 想調用一個類方法,首先是Student的類對象 根據isa指針去Student的元類對象中查找有沒有對應的類方法 沒有的話Student的元類對象會根據自己的superclass指針去父類的元類對象(也就是Person的元類對象)中查找有沒有對應的類方法,在沒有的話Person的元類對象再根據自己的superclass指針去NSObject的元類對象中尋找 有的話進行調用 沒有的話NSObject的元類對象會根據superclass指針去NSObject的類對象中去尋找是否有相同名稱的對象方法(這個地方下面會具體講到為什么基類的superclass指針會指向對應的類對象)


關於上面提到的為什么基類的superclass指針為什么在找不到方法的時候會指向基類的類對象 也就是為什么沒有找到對應的類方法的情況下卻可以調用同名對象方法?
關於這一點我們通過代碼來驗證:
首先我們新建一個NSObject的分類,在.h文件中聲明一個test的類方法,但在.m文件中並未實現這個類方法 而是實現了同名的對象方法()
#import "NSObject+Test.h" @implementation NSObject (Test) //+ (void)test //{ // NSLog(@"+[NSObject test] - %p", self); //} - (void)test { NSLog(@"-[NSObject test] - %p", self); } @end
我們調用類方法發現,及時沒有對應的類方法,程序也可以正常運行,並且成功調用了同名的對象方法:

假如我們在m文件沒有實現同名test的對象方法,那么程序會報錯的:
+[NSObject test]: unrecognized selector sent to class 0x7fffaddd7140
關於在h文件中有類方法的聲明,這個是沒有影響的 因為沒有這個聲明的話程序根本跑不起來 我們關注的點是基類的superclass指針為什么在找不到方法的時候會指向基類的類對象尋找同名的對象方法
比如我們在h文件中聲明了test的對象方法 m文件沒有實現test方法 同樣會報unrecognized錯 這就是因為基類的對象方法中找不到方法后直接返回空值 而不是像類方法一樣從元類對象找不到再去到類對象找同名對象方法
關於基類的superclass指針為什么在找不到方法的時候會指向基類的類對象,這是因為oc在調用方法的時候實際上是轉換為c/c++去底層實現的 但是c/c++的底層實現並沒有區分類方法還是對象方法 也就是沒有區分+-號
比如
[NSObject test];
實際上是轉換為了
objc_msgSend([NSObject class], @selector(test))
沒有區分+-號 所以在基類元類對象沒有找到對應的類方法后回去基類的類對象中查看是否有同名的對象方法 有的話就調用 再沒有的話就報錯了
NSObject本質揭秘
查看OC對象占用字節數
