OC對象的本質(上):OC對象的底層實現原理
OC對象的本質(中):OC對象的種類
OC對象的本質(下):詳解isa&superclass指針
isa指針
先總結一下我們在對象的分類一文里面分析過的問題,OC對象氛圍三類
instance對象
,內部包含
- 成員變量
- 特殊的成員變量isa指針
class對象
,用來描述instance對象,內部包含
- isa指針
- superclass指針
- 屬性信息
- 對象方法信息(-方法)
- 協議信息
- instance對象的成員變量的描述信息
meta-class對象
,用來存放類方法,內部包含
- isa指針
- superclass指針
- 類方法信息(+方法)
對一個類來說,它的instance
、class
、mete-class
對象之間,一定是有某種聯系的。假設這種聯系不存在,我們看看會碰到什么問題。比如我調用一個instance對象的方法
[InstanceObj InstanceObjMethod];
它的底層是
objc_msgSend(instanceObj, @sel_registerName("instanceObjMethod"));
也就是給instanceObj對象發消息。
在instance對象
不跟外界關聯的情況下,它內部只有一些成員變量信息,是不可能完成方法調用的,因為對象方法是存放在class對象
里面的,對class對象
調用+方法
的時候也是一樣,必須有辦法跟meta-class對象
關聯起來,才能完成對+方法
的調用。所以isa指針,就只它們之間的關聯。可以通過下圖來理解isa的作用。
大致可以歸納為
-
instance對象
的isa
指針指向class對象
。當調用對象方法(-方法)時,通過instance
的isa
找到class
,然后在class
的方法列表里面找到對應的實現進行調用。 -
class對象
的isa指針指向meta-class對象
。當調用類方法(+)方法時,通過class
的isa
找到meta-class
,最后在meta-class
的方法列表找到對應的實現進行調用。
那么通過isa的橋接作用,我夢應該能更近一步地理解OC消息發送以及方法調用的過程了。
superclass指針
顯而易見,從字面意思,我們就能知道,superclass就是父類的意思。
假定我們有以下幾個類
@interface Person : NSObject
@end
@interface Student : Person
@end
我們知道superclass
指針存在于class對象
和meta-class對象
里面。我們根據接下來的圖示來闡述一下:
class的superclass指針
一個類的class對象
里面的superclass指針
指向該類的父類的class對象
當Student
的instance對象
要調用Person
的對象方法時,會先通過isa
找到Student
的class對象
,然后通過這個class對象
的superclass
找到Person(Student的父類)
的class對象
,最后找到相應的對象方法(-方法)的實現進行調用
meta-class的superclass指針
一個類的meta-class里面的superclass指針指向該類的父類的meta-class對象
當Student
的class對象
要調用Person
的類方法時,會先通過isa
找到Student
的meta-class對象
,然后通過這個meta-class對象
的superclass
找到Person(Student的父類)
的meta-class對象
,最后找到相應的類方法(+方法)的實現進行調用
isa、superclass總結
上圖來自蘋果官方,完整描述了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指針指向哪里?
根據我們上面的梳理和總結,我們可以得出結論
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的細節就行。
深度窺探class/meta-class的內部結構----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
對象中
OC對象的本質(上):OC對象的底層實現
OC對象的本質(中):OC對象的分類
OC對象的本質(下):詳解isa&superclass指針
特別備注
本系列文章總結自MJ老師在騰訊課堂開設的OC底層原理課程,相關圖片素材均取自課程中的課件。