
前一篇講解了一下Runtime的底層原理,objc_msgSend的消息發(fā)送流程;其實(shí)學(xué)習(xí)Runtime,首先要了解它底層的一些常用數(shù)據(jù)結(jié)構(gòu),比如isa指針;在arm64架構(gòu)之前,isa就是一個(gè)普通的指針,存儲(chǔ)著Class、Meta-Class對(duì)象的內(nèi)存地址;從arm64架構(gòu)開(kāi)始,對(duì)isa進(jìn)行了優(yōu)化,變成了一個(gè)共用體(union)結(jié)構(gòu),還使用位域來(lái)存儲(chǔ)更多的信息。
共用體:
共用體把幾種不同數(shù)據(jù)類型的變量存放在同一塊內(nèi)存里。共用體中的變量共享同一塊內(nèi)存。
union的主要特征有:
- union中可以定義多個(gè)成員,union的大小由最大的成員的大小決定;
- union成員共享同一塊大小的內(nèi)存,一次只能使用其中的一個(gè)成員;
- 對(duì)union某一個(gè)成員賦值,會(huì)覆蓋其他成員的值(但前提是成員所占字節(jié)數(shù)相同,當(dāng)成員所占字節(jié)數(shù)不同時(shí)只會(huì)覆蓋相應(yīng)字節(jié)上的值,比如對(duì)char成員賦值就不會(huì)把整個(gè)int成員覆蓋掉,因?yàn)閏har只占一個(gè)字節(jié),而int占四個(gè)字節(jié));
- union量的存放順序是所有成員都從低地址開(kāi)始存放的。
isa的結(jié)構(gòu)如下:
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if SUPPORT_PACKED_ISA
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL //這個(gè)很重要后面會(huì)講到??
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
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__
...下面代碼省略不做重點(diǎn)介紹了??
};
isa參數(shù)詳解
- nonpointer:
0,代表普通的指針,存儲(chǔ)著Class、Meta-Class對(duì)象的內(nèi)存地址
1,代表優(yōu)化過(guò),使用位域存儲(chǔ)更多的信息 - has_assoc:是否有設(shè)置過(guò)關(guān)聯(lián)對(duì)象,如果沒(méi)有,釋放時(shí)會(huì)更快
- has_cxx_dtor:是否有C++的析構(gòu)函數(shù)(.cxx_destruct),如果沒(méi)有,釋放時(shí)會(huì)更快
- shiftcls:存儲(chǔ)著Class、Meta-Class對(duì)象的內(nèi)存地址信息
- magic:用于在調(diào)試時(shí)分辨對(duì)象是否未完成初始化
- weakly_referenced:是否有被弱引用指向過(guò),如果沒(méi)有,釋放時(shí)會(huì)更快
- deallocating:對(duì)象是否正在釋放
- extra_rc:表示該對(duì)象的引用計(jì)數(shù)值,實(shí)際上是引用計(jì)數(shù)值減 1,例如,如果對(duì)象的引用計(jì)數(shù)為 10,那么 extra_rc 為 9。如果引用計(jì)數(shù)大于 10,則需要使用到下面的 has_sidetable_rc。
- has_sidetable_rc:當(dāng)對(duì)象引用計(jì)數(shù)大于 10 時(shí),則has_sidetable_rc 的值為 1,那么引用計(jì)數(shù)會(huì)存儲(chǔ)在一個(gè)叫 SideTable 的類的屬性中,這是一個(gè)散列表。原文鏈接
扯了這么多到底什么是isa?官方介紹是這樣的:
Every object is connected to the run-time system through itsisa instance variable, inherited from the NSObject class.isa identifies the object's class; it points to a structurethat's compiled from the class definition. Through isa, anobject can find whatever information it needs at run timesuch asits place in the inheritance hierarchy, the size and structure ofits instance variables, and the location of the methodimplementations it can perform in response to messages.
(每個(gè)對(duì)象都通過(guò)從NSObject類繼承的實(shí)例變量itsisa連接到運(yùn)行時(shí)系統(tǒng)。isa標(biāo)識(shí)對(duì)象的類;它指向一個(gè)從類定義編譯而來(lái)的結(jié)構(gòu)。通過(guò)isa,一個(gè)對(duì)象可以在運(yùn)行時(shí)找到它需要的任何信息,比如它在繼承層次結(jié)構(gòu)中的位置、它的實(shí)例變量的大小和結(jié)構(gòu),以及它在響應(yīng)消息時(shí)可以執(zhí)行的methodimplementations的位置。)
一個(gè)對(duì)象(Object)的isa指向了這個(gè)對(duì)象的類(Class),而這個(gè)對(duì)象的類(Class)的isa指向了metaclass。這樣我們就可以找到靜態(tài)方法和變量了。Objective-C的運(yùn)行時(shí)是動(dòng)態(tài)的,它能讓你在運(yùn)行時(shí)為類添加方法或者去除方法以及使用反射(反射什么鬼?傳送門)。
講isa就一定會(huì)提到metaclass,這里先提一下什么是metaclass?
- meta-class是一個(gè)類對(duì)象的類
- 當(dāng)我們向一個(gè)對(duì)象發(fā)送消息時(shí),runtime會(huì)在這個(gè)對(duì)象所屬的這個(gè)類的方法列表中查找方法;而向一個(gè)類發(fā)送消息時(shí),會(huì)在這個(gè)類的meta-class的方法列表中查找。
- meta-class 是必須的,因?yàn)樗鼮橐粋€(gè) Class 存儲(chǔ)類方法。每個(gè)Class都必須有一個(gè)唯一的 meta-class,因?yàn)槊總€(gè)Class的類方法基本不可能完全相同。
問(wèn)題提出:iOS中有靜態(tài)方法與動(dòng)態(tài)方法,那么兩種方法的異同是什么?(問(wèn)題來(lái)源:腳本之家-lqh )
因?yàn)槊總€(gè)對(duì)象都由相應(yīng)的數(shù)據(jù)結(jié)構(gòu)與方法相構(gòu)成,一個(gè)程序可能有多個(gè)屬于同一個(gè)類的對(duì)象,而每個(gè)對(duì)象的數(shù)據(jù)結(jié)構(gòu)應(yīng)該是不一的,但方法是相同的,若為每個(gè)對(duì)象開(kāi)辟內(nèi)存空間來(lái)存儲(chǔ)方法,必然是對(duì)內(nèi)存空間極大的浪費(fèi)。因此apple是通過(guò)類對(duì)象與元類來(lái)解決這個(gè)問(wèn)題的。
對(duì)象的底層實(shí)際上就是結(jié)構(gòu)體,其有兩個(gè)重要的指針,一個(gè)是isa指針,一個(gè)是super指針。
isa指針:負(fù)責(zé)指向類對(duì)象,用來(lái)表明自己是什么類類型,并能調(diào)用類對(duì)象中的動(dòng)態(tài)方法。
super指針:表示對(duì)象的繼承關(guān)系,指向父類,從而能調(diào)用父類的相應(yīng)方法。
類對(duì)象:類對(duì)象是由元類生成的對(duì)象,負(fù)責(zé)存儲(chǔ)動(dòng)態(tài)方法,動(dòng)態(tài)方法在編譯器是不確定的,因此編譯器也無(wú)法檢測(cè)與動(dòng)態(tài)方法相關(guān)的錯(cuò)誤,動(dòng)態(tài)方法的調(diào)用是在運(yùn)行期中通過(guò)消息機(jī)制來(lái)執(zhí)行的,因此也只有運(yùn)行期才會(huì)報(bào)錯(cuò)。

結(jié)論:
兩者的差異包括:
- 方法列表是區(qū)分開(kāi)的,分別存儲(chǔ)在類對(duì)象與元類中。
- 動(dòng)態(tài)方法是在運(yùn)行期中調(diào)用,編譯器無(wú)法檢測(cè)錯(cuò)誤,靜態(tài)方法是在編譯器就確定,編譯器能檢測(cè)錯(cuò)誤。
- 動(dòng)態(tài)方法由對(duì)象調(diào)用,靜態(tài)方法由類調(diào)用,因?yàn)檎{(diào)用方法是通過(guò)isa和super指針實(shí)現(xiàn)的。因此對(duì)象只能調(diào)用類對(duì)象的方法,而類對(duì)像能調(diào)用元類的方法。

這張圖描述如下:
- 類的實(shí)例對(duì)象的 isa 指向它的類;類的 isa 指向該類的 metaclass;
- 類的 super_class 指向其父類,如果該類為根類則值為 NULL;
- metaclass 的 isa 指向根 metaclass,如果該 metaclass 是根 metaclass則指向自身;
- metaclass 的 super_class 指向父 metaclass,如果該 metaclass 是根 metaclass則指向該 metaclass 對(duì)應(yīng)的類;
- Object-C 為每個(gè)類的定義生成兩個(gè) objc_class ,一個(gè)普通的 class,另一個(gè)即metaclass。我們可以在運(yùn)行期創(chuàng)建這兩個(gè) objc_class 數(shù)據(jù)結(jié)構(gòu),然后使用 objc_addClass將 class注冊(cè)到運(yùn)行時(shí)系統(tǒng)中,以此實(shí)現(xiàn)動(dòng)態(tài)地創(chuàng)建一個(gè)新的類。
為什么這里說(shuō)生成兩個(gè) objc_class ,從前面metaclass就可以了解一二了。講到這里,大家可能很疑惑isa到底是怎么指向類的,解釋如下:isa里面存儲(chǔ)各種信息,是一個(gè)共用體,其中shiftcls 33位才是用來(lái)存放地址,通過(guò)&ISA_MASK就可以將33位的地址值取出來(lái),看圖分析理解更透徹:

因?yàn)橄旅嬉玫絚lass 講解,我們先來(lái)看看 objc_class 的定義,然后來(lái)個(gè)實(shí)例和圖解進(jìn)行分析:
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;
稍微解釋一下各個(gè)參數(shù)的意思:
- isa:是一個(gè)Class 類型的指針. 每個(gè)實(shí)例對(duì)象有個(gè)isa的指針,他指向?qū)ο蟮念?,而Class里也有個(gè)isa的指針, 指向meteClass(元類)。元類保存了類方法的列表。當(dāng)類方法被調(diào)用時(shí),先會(huì)從本身查找類方法的實(shí)現(xiàn),如果沒(méi)有,元類會(huì)向他父類查找該方法。同時(shí)注意的是:元類(meteClass)也是類,它也是對(duì)象。元類也有isa指針,它的isa指針最終指向的是一個(gè)根元類(root meteClass).根元類的isa指針指向本身,這樣形成了一個(gè)封閉的內(nèi)循環(huán)。
- super_class:父類,如果該類已經(jīng)是最頂層的根類,那么它為NULL。
- version:類的版本信息,默認(rèn)為0
- info:供運(yùn)行期使用的一些位標(biāo)識(shí)。
- instance_size:該類的實(shí)例變量大小
- ivars:成員變量的數(shù)組
下面來(lái)個(gè)例子:
- 新建一個(gè)類Parent,繼承于NSObject, 里面有成員方法-(void)selectorP,類方法+(void)ClassSelectorP。
- 新建一個(gè)類Child,繼承于Parent,里面有成員方法-(void)selectorC, 類方法+(void)ClassSelectorC.
現(xiàn)在我們新建一個(gè)實(shí)例Child* child = [Chlid new];
- 當(dāng)我們調(diào)用[child class] 的時(shí)候,child就會(huì)通過(guò)isa指針去找到Child。
- 當(dāng)我們調(diào)用[child superclass]的時(shí)候,child 通過(guò)isa找到Child,再通過(guò)Child的isa,找到Parent。
對(duì)象方法 - 接著,調(diào)用[child SelectorC],child通過(guò)isa找到Child,在Child的方法列表里面找到SelectorC;
- 再試著調(diào)用[child SelectorP],child通過(guò)isa找到Child,發(fā)現(xiàn)Child里面并沒(méi)有這個(gè)方法,再通過(guò)Child的isa,找到Parent,在Parent里面的方法列表找到了SelectorP;
類方法 - 再是類方法[Child ClassSelectorC],Child(請(qǐng)注意是類調(diào)用)通過(guò)isa找到Child的metaclass,在metaclass的方法列表里面找到了ClassSelectorC;
- 再試著調(diào)用[Child ClassSelectorP],Child通過(guò)isa找到Child的metaclass,發(fā)現(xiàn)metaclass里面并沒(méi)有這個(gè)方法,通過(guò)metaclass里面的isa找到Parent的metaclass,在里面的方法列表找到了ClassSelectorP;
-
所有的元類最終繼承一個(gè)根元類,根元類isa指針指向本身,形成一個(gè)封閉的內(nèi)循環(huán)
圖解如下:
靈魂畫(huà)手??
isa基本就講完了,下面來(lái)個(gè)小李子再次加深理解:
NSArray *array = [[NSArray alloc] init];流程剖析
- [NSArray alloc]先被執(zhí)行。先去NSArray中查找+alloc方法(類方法),因?yàn)镹SArray沒(méi)有+alloc方法,于是去父類NSObject去查找。
- 檢測(cè)NSObject是否響應(yīng)+alloc方法,發(fā)現(xiàn)響應(yīng),于是檢測(cè)NSArray類,并根據(jù)其所需的內(nèi)存空間大小開(kāi)始分配內(nèi)存空間,然后把isa指針指向NSArray類。同時(shí),+alloc也被加進(jìn)cache列表里面。
- 接著,執(zhí)行-init方法,如果NSArray響應(yīng)該方法,則直接將其加入cache;如果不響應(yīng),則去父類查找。
- 在后期的操作中,如果再以[[NSArray alloc] init]這種方式來(lái)創(chuàng)建數(shù)組,則會(huì)直接從cache中取出相應(yīng)的方法,直接調(diào)用。
總結(jié):
- isa是一個(gè)共用體;isa標(biāo)識(shí)對(duì)象的類;它指向一個(gè)從類定義編譯而來(lái)的結(jié)構(gòu)。通過(guò)isa,一個(gè)對(duì)象可以在運(yùn)行時(shí)找到它需要的任何信息,比如它在繼承層次結(jié)構(gòu)中的位置、它的實(shí)例變量的大小和結(jié)構(gòu),以及它在響應(yīng)消息時(shí)可以執(zhí)行的imp的位置;
- 也可以說(shuō)isa是一個(gè)Class 類型的指針,每個(gè)實(shí)例對(duì)象有個(gè)isa的指針,他指向?qū)ο蟮念惤Y(jié)構(gòu)。
- Objective-C 2.0中的描述是:新的對(duì)象被創(chuàng)建,其內(nèi)存同時(shí)被分配,實(shí)例變量也同時(shí)被初始化;對(duì)象的第一個(gè)實(shí)例變量是一個(gè)指向 該對(duì)象的類結(jié)構(gòu)的指針,叫做 isa。通過(guò)該指針,對(duì)象可以訪問(wèn)它對(duì)應(yīng)的類以及相應(yīng)的父類。
文章參考:
http://www.itdecent.cn/p/cf93dc9d2262
http://www.itdecent.cn/p/83b9f172c43c
