iOS學習
- 前言
- 對象的底層結構
- isa的類型isa_t
- objc_class & objc_object
- 類信息的靜態與動態存儲(ro、rw、rwe機制)
- cache
- bits
- 繼承鏈
- isKindOfClass和isMemberOfClass
- `isKindOfClass:`
- `isMemberofClass`
前言
對 對象底層結構的相關信息有點遺忘,簡略記錄一下。順便記錄一下繼承鏈相關。
對象的底層結構
isa指針采用了典型的適配器設計模式。
適配器設計模式(即將底層接口適配為客戶端需要的接口)
,達成上下層接口隔離的目的。
- 上層OC對象 只需要通過 isa、Class、objc_object、objc_class 等接口訪問對象和類信息。 底層實現
- 通過聯合體、結構體、位域等方式,封裝了復雜的內存布局和數據結構。
- 適配器(如isa_t、objc_object、objc_class、cache_t、class_rw_t等)把底層復雜的數據結構和操作,適配成了上層易用的接口,實現了“接口隔離”和“解耦”。
isa的類型isa_t
union isa_t { //聯合體isa_t() { }isa_t(uintptr_t value) : bits(value) { }//提供了cls 和 bits ,兩者是互斥關系,即通過cls初始化,bits無默認值,通過bits初始化,cls無默認值Class cls;uintptr_t bits;
#if defined(ISA_BITFIELD)struct {ISA_BITFIELD; // defined in isa.h 結構體定義的位域,儲存類信息和其他信息。};
#endif
};
位域如下:
nonpointer
有兩個值,表示自定義的類等,占1
位0
:純isa指針
1
:不只是類對象地址
,isa中包含了類信息
、對象的引用計數
等
has_assoc
表示關聯對象標志
位,占1
位0
:沒有關聯
對象1
:存在關聯
對象
has_cxx_dtor
表示該對象是否有C++/OC的析構器
(類似于dealloc
),占1
位- 如果
有
析構函數,則需要做析構
邏輯 - 如果
沒有
,則可以更快的釋放
對象
- 如果
shiftcls
表示存儲類的指針的值
(類的地址), 即類信息arm64
中占33
位,開啟指針優化的情況下,在arm64架構中有33
位用來存儲類指針x86_64
中占44
位
magic
用于調試器判斷當前對象是真的對象
還是沒有初始化的空間
,占6
位weakly_refrenced
是 指對象是否被指向
或者曾經指向一個ARC的弱變量
- 沒有弱引用的對象可以更快釋放
deallocating
標志對象是是否正在釋放
內存has_sidetable_rc
表示 當對象引用計數大于10
時,則需要借用該變量存儲進位
extra_rc
(額外的引用計數) ,表示該對象的引用計數值
,實際上是引用計數值減1- 如果對象的
引用計數為10
,那么extra_rc
為9(這個僅為舉例說明),實際上iPhone 真機上的extra_rc
是使用 19位來存儲引用計數的
- 如果對象的
cls
與 isa
關聯原理
就是isa
指針中的shiftcls
位域中存儲了類信息
,其中initInstanceIsa
的過程是將 calloc
指針 和當前的 類cls
關聯起來。
objc_class & objc_object
所有Class都是以objc_class為模版繼承而來的。isa指針的類型是Class,由objc_class
定義的類型。
而結構體類型objc_class繼承自objc_object。obje_object是一個結構體,且有一個isa屬性,因此objc_class也擁有isa屬性
NSObject
中的isa
在底層是由Class
定義的,其中class
的底層編碼來自 objc_class
類型,所以NSObject
也擁有了isa
屬性
總結
- 所有的
對象
+類
+元類
都有isa
屬性 - 所有的
對象
都是由objc_object
繼承來的,類繼承自objc_class - 簡單概括就是
萬物皆對象
,萬物皆來源于objc_object
,有以下兩點結論:- 所有以
objc_object
為模板 創建的對象
,都有isa
屬性 - 所有以
objc_class
為模板,創建的類
,都有isa
屬性
- 所有以
- 在結構層面可以通俗的理解為
上層OC
與底層
的對接
:下層
是通過結構體
定義的模板
,例如objc_class、objc_object
上層
是通過底層的模板創建
的 一些類型,例如CJLPerson
objc_class
的定義:
struct objc_class : objc_object {// Class ISA; //8字節Class superclass; //Class 類型 8字節cache_t cache; // formerly cache pointer and vtableclass_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
}
-
isa
屬性:繼承自objc_object
的isa
,占8
字節 -
superclass
屬性:Class
類型,Class
是由objc_object
定義的,是一個指針
,占8
字節 -
cache
屬性:簡單從類型class_data_bits_t
目前無法得知,而class_data_bits_t
是一個結構體
類型,結構體
的內存大小
需要根據內部的屬性
來確定,而結構體指針才是8字節
-
bits
屬性:只有首地址
經過上面3個屬性的內存大小總和的平移,才能獲取到bits
類信息的靜態與動態存儲(ro、rw、rwe機制)
初始時,只有ro
。
當需要修改類信息的時候,rw
引用ro
,并拷貝一部分信息。
將需要動態更新的部分提取出來,存?rwe
:
總結:程序加載時方法存在ro
。當類第一次使用的時候,rw
就會引用ro
;如果動態修改,就會從ro
拷貝到rwe
;修改的時候也是去操作rwe
;
rw和ro的區別:ro
放在純凈的內存空間(clean memory),是只讀的。rw
在運行生成,存放在可以讀寫的內存空間中(dirty memory),一經使用,ro
就會成為rw
的一部分(通過指針引用)。并且rw
對于真正需要修改的內容,還會拆分出class_rw_ext_t
;簡稱rwe
。
cache
其中,cache類的內存大小為12+2+2=16字節。分別有:buckets(8字節),mask(4字節)或者_maskAndBuckets(8字節), _mask_unused(4字節) 加上 _flag(2字節), _occupied(2字節)。
注意:buckets是方法緩存中的一個哈希表數組,儲存了SEL和IMP。而mask使用來計算哈希索引的。這兩個可以快速定位方法緩存。
sel-imp
是在cache_t
的_buckets屬性
中,而在cache_t結構體中提供了獲取_buckets
屬性的方法buckets()
我們可以通過相應的獲取方法sel()
以及 imp(pClass)
獲得對應的sel-imp
bits
類的首地址平移32字節即可得到bits,bits儲存信息的類型是class_rw_t
,其中可以獲取屬性列表和方法列表
通過由class_rw_t
提供的propertoes
方法,可以獲取到一個實際類型
為property_array_t
的變量,其中有一個list的類型是property_list_t
,是一個指針,存儲了 property_list
,即屬性列表。所以bits中儲存了屬性列表。
通過由class_rw_t
提供methods
方法, 可以獲取到一個實際類型
為method_array_t
的變量,其中的list指針,類型是method_list_t
,里面就是具體的方法列表
。注意,該列表里面只有實例方法。類方法存儲在元類的bits屬性中,獲取方法與該方法相同,畢竟類是元類的實例。
在class_rw_t中,還有一個ro
方法,其返回類型是class_ro_t
,其中有一個類型為ivar_list_t
的ivars
屬性,其中儲存著成員變量列表。該成員變量列表不僅儲存著{}定義的成員變量,還存儲著屬性定義的成員變量。
繼承鏈
首先是最經典的圖片:
-
isa走位
isa的走向有以下幾點說明:
實例對象(Instance of Subclass)
的isa
指向類(class)
類對象(class)
isa
指向元類(Meta class)
元類(Meta class)
的isa
指向根元類(Root metal class)
根元類(Root metal class)
的isa
指向它自己
本身,形成閉環
,這里的根元類
就是NSObject
superclass走位
superclass(即繼承關系)的走向也有以下幾點說明:
-
類之間的繼承關系
類(subClass)
繼承自父類(superClass)
父類(superClass)
繼承自根類(RootClass)
,此時的根類是指NSObject
根類
繼承自nil
,所以根類
即NSObject
可以理解為萬物起源
,即無中生有
-
元類
也存在繼承
,元類之間的繼承關系如下:-
子類的元類(metal SubClass)
繼承自父類的元類(metal SuperClass)
-
父類的元類(metal SuperClass)
繼承自根元類(Root metal Class
-
根元類(Root metal Class)
繼承于根類(Root class)
,此時的根類是指NSObject
-
-
【注意】
實例對象
之間沒有繼承關系
,類
之間有繼承關系
isKindOfClass和isMemberOfClass
isKindOfClass:
- 一切皆從調用者obj的isa開始,然后順著superclass走下去,直到找到cls或superclass為nil結束
- 當superclass為nil,意味著最后的根類NSObject也不是cls,返回flase。
類調用:
判斷順序: SubClass -> MetaClass->MetaClass->...->RootMetaClass->NSObject
其中, 類對象調用方法的本質 是判斷 cls
是不是 元類的繼承鏈
上的任意一個
元類調用:
MetaClass
的 ISA
指向 RootMetaClass
,所以從 RootMetaClass
開始比較判斷是不是我們傳入的cls,如果不是,再看根類NSObject是不是,如果NSObject也不是,就徹底沒有了,返回false,
根元類的父類是NSObject
cls判斷順序如圖:MetaClass -> RootMetaClass->NSObject
**對象調用:**cls判斷順序如圖所示:object -> SubClass -> SubClass ->...->NSObject
本質是判斷 cls
是不是 類繼承鏈
上的任意一個
isMemberofClass
不用像isKindOfClass循環直到找到或nil,他只要比較cls是不是我當前的isa指向,是返回true,不是返回false。
這個方法單純地用來判斷,cls是不是調用者的isa !!
類對象調用:
傳入任何類對象都是false,因為類的isa指向元類
元類對象調用:
元類的isa指向根元類NSObjcet,傳入任何類對象也都是false
對象調用:
只要判斷對象的isa,也就是圖中的SubClass是不是我們傳入的cls
- 只判斷自己的類對象是不是傳入的cls
- 只接受類對象傳入 ,因為沒有isa,不存在元類的介入
總結
isMemberOfClass
-
對象調用:判斷對象的isa(類對象)是否等于傳入的cls,只判斷本類,不查父類。
- 查詢鏈:對象 → isa(類對象)== cls ?
-
類對象調用:判斷類對象的isa(元類)是否等于cls。
- 查詢鏈:類對象 → isa(元類)== cls ?
-
元類對象調用:判斷元類對象的isa(根元類)是否等于cls。
- 查詢鏈:元類對象 → isa(根元類)== cls ?
isKindOfClass
-
對象調用:判斷對象的isa(類對象)是否等于cls,不等則沿superclass鏈查找父類,直到NSObject或nil。
- 查詢鏈:對象 → isa(類對象)→ superclass → … → NSObject → nil
-
類對象調用:判斷類對象的isa(元類)及其superclass鏈是否等于cls,最終會查到NSObject。
- 查詢鏈:類對象 → isa(元類)→ superclass(父類元類)→ … → 根元類 → superclass(NSObject類對象)→ nil
-
元類對象調用:判斷元類對象的isa(根元類)及其superclass鏈是否等于cls,最終會查到NSObject類對象。
- 查詢鏈:元類對象 → isa(根元類)→ superclass(NSObject類對象)→ nil