前一篇講解了一下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í)為類添加方法或者去除方法以及使用反射(反射什么鬼座咆?傳送門(mén))品姓。
講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ū)ο蟮念悾鳦lass里也有個(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.reibang.com/p/cf93dc9d2262
http://www.reibang.com/p/83b9f172c43c