OC對象的本質(中):OC對象的種類
github
先總結一下咱們在對象的分類一文裏面分析過的問題,OC對象氛圍三類面試
instance對象
,內部包含markdown
class對象
,用來描述instance對象,內部包含app
meta-class對象
,用來存放類方法,內部包含ide
對一個類來講,它的instance
、class
、mete-class
對象之間,必定是有某種聯繫的。假設這種聯繫不存在,咱們看看會碰到什麼問題。好比我調用一個instance對象的方法oop
[InstanceObj InstanceObjMethod];
複製代碼
它的底層是post
objc_msgSend(instanceObj, @sel_registerName("instanceObjMethod"));
複製代碼
也就是給instanceObj對象發消息。ui
在instance對象
不跟外界關聯的狀況下,它內部只有一些成員變量信息,是不可能完成方法調用的,由於對象方法是存放在class對象
裏面的,對class對象
調用+方法
的時候也是同樣,必須有辦法跟meta-class對象
關聯起來,才能完成對+方法
的調用。因此isa指針,就只它們之間的關聯。能夠經過下圖來理解isa的做用。spa
大體能夠概括爲
instance對象
的isa
指針指向class對象
。當調用對象方法(-方法)時,經過instance
的isa
找到class
,而後在class
的方法列表裏面找到對應的實現進行調用。class對象
的isa指針指向meta-class對象
。當調用類方法(+)方法時,經過class
的isa
找到meta-class
,最後在meta-class
的方法列表找到對應的實現進行調用。那麼經過isa的橋接做用,我夢應該能更近一步地理解OC消息發送以及方法調用的過程了。
顯而易見,從字面意思,咱們就能知道,superclass就是父類的意思。 假定咱們有如下幾個類
@interface Person : NSObject
@end
@interface Student : Person
@end
複製代碼
咱們知道superclass
指針存在於class對象
和meta-class對象
裏面。咱們根據接下來的圖示來闡述一下:
一個類的class對象
裏面的superclass指針
指向該類的父類的class對象
當Student
的instance對象
要調用Person
的對象方法時,會先經過isa
找到Student
的class對象
,而後經過這個class對象
的superclass
找到Person(Student的父類)
的class對象
,最後找到相應的對象方法(-方法)的實現進行調用
一個類的meta-class裏面的superclass指針指向該類的父類的meta-class對象 當Student
的class對象
要調用Person
的類方法時,會先經過isa
找到Student
的meta-class對象
,而後經過這個meta-class對象
的superclass
找到Person(Student的父類)
的meta-class對象
,最後找到相應的類方法(+方法)的實現進行調用
上圖來自蘋果官方,完整描述了isa、superclass指針的做用,爲了更加便於理解,咱們在後面的圖例中用Student代替subclass,Person代替superclass,NSObject代替rootclass。
instance
的isa
指向class
class
的isa
指向meta-class
meta-class
的isa
指向基類的meta-class
class
的superclass
指向父類的class
,若是沒有父類,superclass
指針爲nil
meta-class
的superclass
指向父類的meta-class
,基類的meta-class
的superclass
指向基類的class
instance調用對象方法的軌跡 咱們以
[student abc];
爲例,student
是Student
類的實例對象,調用軌跡以下圖
對於
student
來講,並不知道abc
方法在哪裏,惟一知道的就是能夠去它的class對象
裏面找,
- 因而先經過
isa
指針進入Student
類的class對象
,若是在其中找到了abc
就直接進行調用,調用過程結束,- 沒找到的話,就經過
class對象
的superclass
指針進入Student
類的父類,也就是Person
類的class對象
,重複上一步的查找邏輯- 以此類推,一層一層往上尋找,若是最終到了基類,也就是
NSObject
類的class對象
裏面,還沒找到的話,因爲它的superclass
爲nil
,最終就會碰到一個經典的報錯[ERROR: unrecognized selector sent to instance]
,調用軌跡結束
class調用類方法的軌跡 咱們以
[Student abc];
爲例調用軌跡圖以下
對與Student類
來講,abc
在哪也是不知道的,咱們知道類方法被規定放在meta-class對象
裏面,因此
- 首先,經過
Student
的class對象
的isa指針
找到其meta-class對象
,而後在方法列表裏面尋找是否有abc
,有的話就調用,調用邏輯結束。- 沒有的話,就經過
meta-class對象
的superclass指針
找到Student
的父類Person
的meta-class對象
,而後查找abc
方法,找到就調用,結束調用軌跡- 沒有的話,就經過
Person
的meta-class對象
的superclass指針
,重複上一步的流程- 一次類推,經過
meta-class對象
的superclass指針
,一層層往上查找- 若是到了基類(
NSObject
)的meta-class
還沒可以找到abc
,此時比較特殊,接下來的superclass指針
會找到NSObjec
t的class對象
,你可能會奇怪,咱們調用一個類方法,怎麼跑到class對象
裏面來了,先保留你的疑問,只需記住,蘋果確實是這麼設計的,此時會繼續在NSObjec
t的class對象
裏面,尋找abc
,若是真的找到了abc
,就會調用- 若是尚未找到,因爲此時的
superclass
是nil
,最終系統將給出報錯
根據咱們上面的梳理和總結,咱們能夠得出結論
isa(of instance) --> isa(of class) --> isa(of meta-class)
下面咱們經過代碼來驗證一下
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface CLPerson : NSObject <NSCopying>
@end
@implementation CLPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
CLPerson *person = [[CLPerson alloc] init];
Class personClass = [CLPerson class];
Class personMetaClass = object_getClass(personClass);
NSLog(@"%p %p %p", person, personClass, personMetaClass);
}
return 0;
}
複製代碼
咱們在代碼中加入斷點,經過控制檯查看一下person
的isa
信息。可是貌似系統只給出了有限信息 還有個辦法,能夠右擊紅框中的
isa
,下拉菜單第一個有個打印功能Print Description of "xxx"
,能夠獲得更爲詳細的輸出 看起來結果仍然被系統包裹了一層 若是你習慣直接在代碼上快捷操做,也能夠這麼作試試
但我仍是喜歡用LLDB來查看,便於比較,和複製數據。
經過
p/x
命令來打印指針,/
後面是打印參數,x
參數表示用16進制數輸出。由於咱們知道person
這個instance
的結構體的包含一個isa
成員變量,person
自己就是指針,因此能夠經過person->isa
訪問isa
的值。 代碼裏面,personClass
是Person類
的class對象
,輸出結果顯示, person的isa = 0x001d8001000014d1
personClass = 0x00000001000014d0
它倆。。。並不相等!!! 這是什麼狀況?不是說好了instance對象
的isa
指向class
對象嘛?
其實在64位機器出現以前,instance對象
的isa
確實是直接指向class對象
的, 也就是
person->isa == personClass
, 從64bit開始,isa
須要進行一次爲運算,才能計算出真實的class對象
地址,系統給咱們提供了一個ISA_MASK,這個能夠在objc4源碼裏面找到。我先直接貼出來
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD \ uintptr_t nonpointer : 1; \ uintptr_t has_assoc : 1; \ uintptr_t has_cxx_dtor : 1; \ uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \ uintptr_t magic : 6; \ uintptr_t weakly_referenced : 1; \ uintptr_t deallocating : 1; \ uintptr_t has_sidetable_rc : 1; \ uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_BITFIELD \ uintptr_t nonpointer : 1; \ uintptr_t has_assoc : 1; \ uintptr_t has_cxx_dtor : 1; \ uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \ uintptr_t magic : 6; \ uintptr_t weakly_referenced : 1; \ uintptr_t deallocating : 1; \ uintptr_t has_sidetable_rc : 1; \ uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
# else
# error unknown architecture for packed isa
# endif
複製代碼
你們請看清這裏是分了arm64和x86_64的,分別對應的是移動設備開發和mac開發。個人代碼是一個mac命令行工程,因此咱們用x86的這個值來試一下 能夠看到,結果就顯而易見了。經過和ISA_MASK進行一次&運算,咱們獲得了personClass的地址。一樣,咱們來試一下personClass的isa指針。
結果我試圖經過
personClass->isa
先打印出其isa
指針的時候,獲得了錯誤提示,告訴咱們說personClass
的類型Class
不是一個結構體,看不太明白,那就先查看一下Class
的定義,typedef struct objc_class *Class;
,而後在往下看一下objc_class
的細節
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
複製代碼
雖然這個結構體裏面有isa指針,可是尾部的OBJC2_UNAVAILABLE;
提示咱們,這已是過期的API了。 不過咱們在第一篇文章中,已經得出結論,知道class對象裏面第一個成員變量確實是一個isa指針,咱們能夠經過一個小技巧來處理這個問題
struct cl_objc_class {
Class isa;
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
CLPerson *person = [[CLPerson alloc] init];
Class personClass = [CLPerson class];
struct cl_objc_class *personClass2 = (__bridge struct cl_objc_class *)(personClass);
Class personMetaClass = object_getClass(personClass);
NSLog(@"%p %p %p", person, personClass, personMetaClass);
}
return 0;
}
複製代碼
咱們自定義一個struct,包含一個isa指針,而後再借助這個結構體類型來讀取personClass裏面的內容,如上代碼,咱們用personClass2在來嘗試一次 ok,結果顯示,
class對象
的isa指針
通過ISA_MASK
轉換以後,獲得了正確的mete-class對象
的地址。到此,上面的面試題相信你們已經能夠完整回答了。在用一個圖來總結一下就是
你會許還會問,那麼superclass指針呢,是否是也須要一個什麼mask轉換?答案是不須要的,能夠用上面相同的方法進行驗證,這裏不做贅述。總之isa指針稍微特殊一點點,特別記住一下關於ISA_MASK的細節就行。
struct objc_class
在OC對象的本質(一)中,咱們得知了一個事實,在class對象中,存放了一個類的方法列表、屬性信息、協議信息、成員變量信息;在meta-class對象中,存放了類的類信息。可是尚未對其仔細驗證過。下面咱們就來研究一下這個問題。 由於class
和meta-class
的類型都是struct objc_class*
,因此咱們問題的答案,就都在這個objc_class
裏面。上面的段落咱們已經看了它的結構了,很惋惜是一個已經廢棄的API,因此咱們必須去最新的源碼裏面,去看一下它的實現。在objc4源碼裏面,咱們找到以下objc_class
的實現
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
//下面是一大堆的方法
...
...
...
}
複製代碼
這裏的objc_class
是一個C++的結構體,若是對C++不太熟的話,先不用過度糾結,能夠借用OC的類來理解就好了,它們的類似度很高,能夠有成員變臉,也能夠有方法,區別主要是一些成員變量的默認做用域不同。能夠看到objc_class
繼承自objc_object
,咱們能夠在源碼objc-private.h
裏看一下objc_object
的實現
struct objc_object {
private:
isa_t isa;
//剩下的都是方法
...
...
...
}
複製代碼
看得出來,其實就是一個isa指針。因而和objc_class
的內容融合一下,咱們能夠理解成下面的這個結構
struct objc_class {
isa_t isa;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
//下面是一大堆的方法
...
...
...
}
複製代碼
很明顯,objc_class
的內部,頭兩個成員分別是isa和superclass,跑不了。可是下面的好像不是咱們期待的內容,沒看到方法列表、屬性協議信息啥的呀。可是我門能夠看到這裏的第一個方法,返回一個class_rw_t *
,看字面意思,class表明類,rw一般表明讀寫(readwrite),t一般指的是 表信息(table),也就是類的可讀寫信息。那麼咱們有理由懷疑這裏面確定有寶貝。進去看一看 果真,原來方法、屬性、協議信息都放在了這裏。同時,咱們還發現了一個
class_ro_t *ro
,字面就是隻讀表,類對象裏面有什麼信息是隻讀的呢?沒錯,成員變量信息,因而咱們在進去驗證一下 看上去推斷是對的,確實找到成員變量信息。注意一下,這裏的ivars是成員變量的描述信息,如名稱,類型等,只須要一份的,因此存在class對象裏面,成員變量的具體值是存在具體的instance對象裏面的,不要理解混了。
對於meta-class來講,結構上和class是同樣的,只不過有些內容可能用不到,例如屬性,協議列表。meta-class的類方法信息其實就放在咱們剛纔看到的那個方法列表裏面,沒錯。class對象的對象方法信息也正是放在這個方法列表裏的
還有一點就是,怎麼說呢,仍是看圖明白
途中咱們看出來,
objc_class
有個成員變量bits
,正是經過 bits & FAST_DATA_MASK
,將objc_class
和它的可讀寫表關聯起來了。下面我引用大神的一張ppt總結一下struct objc_class
的結構
面試題解答
- 對象的isa指針指向哪裏?
instance
對象的isa
指針指向class
對象class
對象的isa
指針指向meta-class
對象meta-class
對象的isa
指針指向基類(也就是NSObject)的meta-class
對象
- OC的類信息存放在哪裏?
- 對象方法,屬性信息,成員變量信息,協議信息,存放在
class
對象中- 類方法,存放在
meta-class
對象中- 成員變量的具體值,存放在
instance
對象中
本系列文章總結自MJ老師在騰訊課堂開設的OC底層原理課程,相關圖片素材均取自課程中的課件。