[iOS]類和對象的底層探索
文章目錄
- [iOS]類和對象的底層探索
- 繼承鏈(類,父類,元類)
- instance 實例對象
- class 類對象
- meta-class 元類對象
- 對對象、類、元類和分類的探索
- instance 實例對象
- class 類對象
- meta-class 元類對象
- 分類(category)
- isMemberOfClass & isKindOfClass(類和對象)
有些部分前面講的不是很清楚
或者沒講的很仔細的
在這一篇重新拿出來記錄一下
也當作是復習一遍
繼承鏈(類,父類,元類)
先來結論
instance 實例對象
instance對象就是通過alloc方法創建出來的對象,每次調用alloc方法都會生成新的instance對象
instance對象在內存中存放的信息包括
- isa指針
- 其他成員變量
class 類對象
class對象的作用是用來描述一個instance對象,它內部存放一個類的屬性信息(@property)、對象方法信息(instance method)、協議信息(protocol)、成員變量信息(ivar),另外class對象里面還有兩個指針,isa指針 和 superclass指針。
Objective-C類是由Class類型來表示的,它實際上是一個指向objc_class
結構體的指針
類對象就是一個結構體struct objc_class,這個結構體存放的數據稱為元數據(metadata),
該結構體的第一個成員變量也是isa指針,這就說明了Class本身其實也是一個對象,因此我們稱之為類對象
類對象在編譯期產生用于創建實例對象,是單例
meta-class 元類對象
meta-class對象的作用是用來描述一個class對象
跟class一樣,元類對象在內存中也是只有一份的
它內部存儲了 isa指針 + superclass指針 + 類方法信息(+方法)
類對象中的元數據存儲的都是如何創建一個實例的相關信息
那么類對象和類方法應該從哪里創建呢? 就是從isa指針指向的結構體創建
類對象的isa指針指向的我們稱之為元類(metaclass), 元類中保存了創建類對象以及類方法所需的所有信息
來看這張很經典的圖
通過上圖我們可以看出整個體系構成了一個自閉環,struct objc_object結構體實例它的isa指針指向類對象
類對象的isa指針指向了元類,super_class指針指向了父類的類對象
而元類的super_class指針指向了父類的元類,那元類的isa指針又指向了自己
為什么要有元類?
元類(Meta Class)是一個類對象的類。在上面我們提到,所有的類自身也是一個對象,我們可以向這個對象發送消息(即調用類方法)。為了調用類方法,這個類的isa指針必須指向一個包含這些類方法的一個objc_class結構體。這就引出了meta-class的概念,元類中保存了創建類對象以及類方法所需的所有信息。任何NSObject繼承體系下的meta-class都使用NSObject的meta-class作為自己的所屬類,而基類的meta-class的isa指針是指向它自己
OC的類其實也是一個對象
一個對象就要有一個它屬于的類
意味著類也要有一個isa指針指向其所屬的類
那么類的類是什么
就是我們所說的元類(MetaClass)
所以,元類就是類的所屬類
既然元類是個類那元類的類是什么呢?
所有的元類都使用根元類作為他們的類
根元類的 isa 指針指向了它自己
對對象、類、元類和分類的探索
法一
可以打開前面提到過的runtime源碼查看相關定義
法二
將OC代碼轉換為C/C++代碼之后再對其進行研究
法二可行的理由是因為OC本質底層實現轉化其實都是C/C++代碼
咱們今天先通過法二進行探索 同時學習一下轉化OC代碼的操作
gcc -rewrite-objc main.m
使用上面指令可以轉化OC代碼
但是如果你的代碼內引用了UIKit之類的庫
那么會出現這么一條報錯
好 怎么辦呢
可以通過加一些參數指定使用 iOS 系統的 SDK
gcc -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.0.sdk main.m
也可以通過xcrun工具
Xcode安裝的時候順帶安裝了xcrun命令,xcrun命令在clang的基礎上進?了?些封裝,要更好??些
xcrun -sdk iphoneos gcc -rewrite-objc main.m
instance 實例對象
對于我們的MYObject對象
找到它的對應部分
typedef struct objc_object MYObject;
這里使用 typedef 為 struct objc_object 結構體定義了一個別名 MYObject
所以對象本質上就是結構體
typedef struct {} _objc_exc_MYObject;
同樣是使用 typedef 定義了一個名為 _objc_exc_MYObject 的結構體類型,不過這里的結構體沒有具體的成員。
struct MYObject_IMPL {...};
這是定義了一個名為 MYObject_IMPL 的結構體。該結構體包含了另一個結構體 NSObject_IMPL NSObject_IVARS,以及兩個 NSString * 類型的成員變量 _age 和 _Nonnull _name
“IMPL”常見的是“implementation”的縮寫,意思是“實現”。在編程中,它通常用于表示某個類、方法或功能的具體實現部分。例如,可能會有一個接口(interface)定義了一些方法,而“IMPL”后綴的類則是具體實現這些方法的類。
class 類對象
我們接著找到NSObject_IMPL的定義部分
關于NSObject的具體實現部分只有一個Class類型的isa指針
那么再看看Class類型到底是何方神圣
可以看出Class底層是objc_class *
類型
咱們前面講過objc_class結構體本質上代表了類對象
由此回收第一個結論
類對象中的元數據存儲的都是如何創建一個實例的相關信息
同時發現
- id底層實現是
struct objc_object *
類型,定義為了一個Class的指針。 - SEL是
struct objc_selector *
類型 - IMP是
void (*)(void)
函數指針類型
接下來我們要找objc_class *
的定義
但是在這個轉化的文件內 找不到objc_class *
的定義了
好 開始挑源碼來看
定義了洋洋灑灑五百行
方法存儲在公共內存位置,提供給所有的實例對象使用、類對象使用
所以咱除去方法,主要看看其成員變量
struct objc_class : objc_object {// Class ISA;Class superclass;cache_t cache; // formerly cache pointer and vtableclass_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
}
好 可能和大家在其他博客內看到的不太一樣
上面是在2006年蘋果發布Objc 2.0之后,objc_class的定義
之前的長這樣
struct objc_class {Class isa OBJC_ISA_AVAILABILITY;#if !__OBJC2__Class super_class OBJC2_UNAVAILABLE;const char *name OBJC2_UNAVAILABLE;long version OBJC2_UNAVAILABLE;long info OBJC2_UNAVAILABLE;long instance_size OBJC2_UNAVAILABLE;struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;struct objc_method_list **methodLists OBJC2_UNAVAILABLE;struct objc_cache *cache OBJC2_UNAVAILABLE;struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif} OBJC2_UNAVAILABLE;
分析一下
superclass
指針指向該類的父類,通過它可以建立類的繼承關系。
cache
(以前是緩存指針和虛函數表)用于存儲方法等的緩存信息,以提高方法查找速度。
bits
通過位運算等方式存儲和管理了一些相關信息及自定義標志,例如類的讀寫權限、是否有自定義的內存分配標志等。
雖然這些成員變量本身并不直接包含所有的類信息,但通過它們以及與之相關的各種方法和運行時機制,可以訪問到類的其他詳細信息,如屬性、方法列表、協議列表等。這些信息可能通過間接的方式存儲、計算或在運行時動態獲取。
將源碼的定義轉化為類圖
NSObject本質上是一個叫做NSObject_IMPL的結構體
其成員變量isa本質上也是一個指向objc_class結構體的指針(objc_class繼承自objc_object結構體,內部有一個isa成員)
meta-class 元類對象
其實元類和類的本質都是objc_class結構體,只不過它們的用途不一樣,類的methods成員變量里存儲著該類所有的實例方法信息,而元類的methods成員變量里存儲著該類所有的類方法信息
分類(category)
分類是OC的一個高級特性,我們一般用它給系統的類或者第三方庫的類擴展方法,屬性和協議,或者把一個類不同功能分散到不同的模塊中實現
來看看它的源碼
同樣屬于結構體
說法一
可以看到里面是沒有成員變量列表存在的,這樣可以解釋分類為什么不能添加成員變量
說法二
因為category是運行時添加的,他只能操作class_rw_t結構,但是class_rw_t結構沒有添加成員變量的入口。成員變量是存儲在class_ro_t中的,是無法被修改的,所以category就不能添加成員變量。
在 Objective-C 中,分類(category)不能添加成員變量。這是因為分類在運行時動態添加,它只能操作 class_rw_t 結構,而 class_rw_t 結構沒有提供添加成員變量的入口。成員變量的信息是存儲在 class_ro_t 中的,并且在運行時是不可修改的。第一種說法不準確,不能僅僅因為源碼中分類的定義沒有成員變量列表就得出分類不能添加成員變量的結論,關鍵在于運行時的結構和機制限制
哦對了要注意有一個點
分類中的方法與原始類以及父類方法相比具有更高優先級,如果覆蓋父類的方法,可能導致super消息的斷裂。因此,最好不要覆蓋原始類中的方法
我們知道一個類的實例方法存儲在類里面,所有的類方法在元類里面,而對象調用方法isa指針先到相應的類的和元類,然后在其方法列表中查找
那么分類是這么實現這一功能的?
留個懸念 這周進度不夠了 我得先寫其他博客
isMemberOfClass & isKindOfClass(類和對象)
老規矩 上總結
圖解isKindOfClass和isMemberOfClass方法
總結
當調用者是——類對象SubClass
+(BOOL)isKindOfClass:(Class)cls :
判斷cls是不是 元類->父類的元類->父父類的元類->…->根元類->NSObject (元類的superclass繼承鏈)其中一個。
cls 傳除NSObject.class外的任意類對象均為false。
+(BOOL)isMemeberOfClass:(Class)cls :
判斷cls是不是自己的isa,
當調用者是——元類MetaClass
+(BOOL)isKindOfClass:(Class)cls :
判斷cls是不是 根元類->NSObject 中的任意一個
+(BOOL)isMemeberOfClass:(Class)cls :
判斷cls是不是根元類
當調用者是——對象Obj
-(BOOL)isKindOfClass:(Class)cls :
判斷cls是不是類對象->父類->…->NSObject(superclass繼承鏈)其中一個
-(BOOL)isMemeberOfClass:(Class)cls :
判斷 cls 是不是自己的 類(類對象)