簡介
Objective-C語言是一門動態(tài)語言历谍,它將很多靜態(tài)語言在編譯和鏈接時期做的事放到了運行時來處理。這種動態(tài)語言的優(yōu)勢在于:我們寫代碼時更具靈活性辣垒,如我們可以把消息轉(zhuǎn)發(fā)給我們想要的對象望侈,或者隨意交換一個方法的實現(xiàn)等。
這種特性意味著Objective-C不僅需要一個編譯器勋桶,還需要一個運行時系統(tǒng)來執(zhí)行編譯的代碼脱衙。對于Objective-C來說,這個運行時系統(tǒng)就像一個操作系統(tǒng)一樣:它讓所有的工作可以正常的運行例驹。這個運行時系統(tǒng)即Objc Runtime捐韩。Objc Runtime其實是一個Runtime庫,它基本上是用C和匯編寫的鹃锈,這個庫使得C語言有了面向?qū)ο蟮哪芰Α?/p>
Runtime庫主要做下面幾件事:
1荤胁、封裝:在這個庫中,對象可以用C語言中的結(jié)構(gòu)體表示屎债,而方法可以用C函數(shù)來實現(xiàn)仅政,另外再加上了一些額外的特性垢油。這些結(jié)構(gòu)體和函數(shù)被runtime函數(shù)封裝后,我們就可以在程序運行時創(chuàng)建圆丹,檢查滩愁,修改類、對象和它們的方法了
2辫封、找出方法的最終執(zhí)行代碼:當(dāng)程序執(zhí)行[object doSomething]時硝枉,會向消息接收者(object)發(fā)送一條消息(doSomething),runtime會根據(jù)消息接收者是否能響應(yīng)該消息而做出不同的反應(yīng)秸讹。這將在后面詳細(xì)介紹檀咙。
Objective-C runtime目前有兩個版本:Modern runtime和Legacy runtime。Modern Runtime 覆蓋了64位的Mac OS X Apps璃诀,還有 iOS Apps弧可,Legacy Runtime 是早期用來給32位 Mac OS X Apps 用的,也就是可以不用管就是了劣欢。
與Rutime交互
Objc 從三種不同的層級上與 Runtime 系統(tǒng)進行交互棕诵,分別是通過 Objective-C 源代碼,通過 Foundation 框架的NSObject類定義的方法凿将,通過對 runtime 函數(shù)的直接調(diào)用校套。
1.Objective-C源代碼
大部分情況下你就只管寫你的Objc代碼就行,runtime 系統(tǒng)自動在幕后辛勤勞作著牧抵。還記得引言中舉的例子吧笛匙,消息的執(zhí)行會使用到一些編譯器為實現(xiàn)動態(tài)語言特性而創(chuàng)建的數(shù)據(jù)結(jié)構(gòu)和函數(shù),Objc中的類犀变、方法和協(xié)議等在 runtime 中都由一些數(shù)據(jù)結(jié)構(gòu)來定義妹孙,這些內(nèi)容在后面會講到。(比如objc_msgSend函數(shù)及其參數(shù)列表中的id和SEL都是啥
2.NSObject的方法
Cocoa 中大多數(shù)類都繼承于NSObject
類获枝,也就自然繼承了它的方法蠢正。最特殊的例外是NSProxy,它是個抽象超類省店,它實現(xiàn)了一些消息轉(zhuǎn)發(fā)有關(guān)的方法嚣崭,可以通過繼承它來實現(xiàn)一個其他類的替身類或是虛擬出一個不存在的類,說白了就是領(lǐng)導(dǎo)把自己展現(xiàn)給大家風(fēng)光無限懦傍,但是把活兒都交給幕后小弟去干雹舀。
有的NSObject中的方法起到了抽象接口的作用,比如description方法需要你重載它并為你定義的類提供描述內(nèi)容谎脯。
NSObject還有些方法能在運行時獲得類的信息葱跋,并檢查一些特性,比如class返回對象的類;isKindOfClass: isMemberOfClass:則檢查對象是否在指定的類繼承體系中娱俺;respondsToSelector:檢查對象能否響應(yīng)指定的消息稍味;conformsToProtocol:檢查對象是否實現(xiàn)了指定協(xié)議類的方法;methodForSelector:則返回指定方法實現(xiàn)的地址荠卷。
3.Runtime的函數(shù)
Runtime 系統(tǒng)是一個由一系列函數(shù)和數(shù)據(jù)結(jié)構(gòu)組成模庐,具有公共接口的動態(tài)共享庫。頭文件存放于/usr/include/objc目錄下油宜。許多函數(shù)允許你用純C代碼來重復(fù)實現(xiàn) Objc 中同樣的功能掂碱。雖然有一些方法構(gòu)成了NSObject類的基礎(chǔ),但是你在寫 Objc 代碼時一般不會直接用到這些函數(shù)的慎冤,除非是寫一些 Objc 與其他語言的橋接或是底層的debug工作疼燥。在Objective-C Runtime Reference中有對 Runtime 函數(shù)的詳細(xì)文檔。
Runtime的數(shù)據(jù)類型
Objective-c是發(fā)消息機制蚁堤,而不是簡單的方法調(diào)用
[receiver message]
會被編譯器轉(zhuǎn)化為:
objc_msgSend(receiver, selector)
如果消息含有參數(shù)醉者,則為:
objc_msgSend(receiver, selector, arg1, arg2, ...)
1.SEL
objc_msgSend函數(shù)第二個參數(shù)類型為SEL,它是selecto在Objc中的表示類型(Swift中是Selector類)披诗。selector是方法選擇器撬即,可以理解為區(qū)分方法的 ID,而這個 ID 的數(shù)據(jù)結(jié)構(gòu)是SEL:
typedef struct objc_selector *SEL;
其實它就是個映射到方法的C字符串呈队,你可以用 Objc 編譯器命令@selector()或者 Runtime 系統(tǒng)的sel_registerName函數(shù)來獲得一個SEL類型的方法選擇器。
不同類中相同名字的方法所對應(yīng)的方法選擇器是相同的宪摧,即使方法名字相同而變量類型不同也會導(dǎo)致它們具有相同的方法選擇器粒竖,于是 Objc 中方法命名有時會帶上參數(shù)類型(NSNumber一堆抽象工廠方法拿走不謝)
我們可以在運行時添加新的selector,也可以在運行時獲取已存在的selector几于,我們可以通過下面三種方法來獲取SEL:
1. sel_registerName函數(shù)
2.Objective-C編譯器提供的@selector()
3.NSSelectorFromString()方法
Id
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
typedef struct objc_object *id;
objc_msgSend第一個參數(shù)類型為id温圆,它是一個指向類實例的指針:
typedef struct objc_object *id;
這里有個objc_object
struct objc_object { Class isa; };
objc_object結(jié)構(gòu)體包含一個指向其類的isa指針,根據(jù)isa指針找到對象所屬的類孩革。
Runtime庫會在類的方法列表及父類的方法列表中去尋找與消息對應(yīng)的selector指向的方法。找到后即運行這個方法得运。
當(dāng)創(chuàng)建一個特定類的實例對象時膝蜈,分配的內(nèi)存包含一個objc_object數(shù)據(jù)結(jié)構(gòu),然后是類的實例變量的數(shù)據(jù)熔掺。NSObject類的alloc和allocWithZone:方法使用函數(shù)class_createInstance來創(chuàng)建objc_object數(shù)據(jù)結(jié)構(gòu)饱搏。
另外還有我們常見的id,它是一個objc_object結(jié)構(gòu)類型的指針置逻。它的存在可以讓我們實現(xiàn)類似于C++中泛型的一些操作推沸。該類型的對象可以轉(zhuǎn)換為任何一種對象,有點類似于C語言中void *指針類型的作用。
Class
之所以說isa是指針是因為Class其實是一個指向objc_class
結(jié)構(gòu)體的指針:typedef struct objc_class *Class;而objc_class就是我們摸到的那個瓜鬓催,里面的東西多著呢:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父類
const char *name OBJC2_UNAVAILABLE; // 類名
long version OBJC2_UNAVAILABLE; // 類的版本信息肺素,默認(rèn)為0
long info OBJC2_UNAVAILABLE; // 類信息,供運行期使用的一些位標(biāo)識
long instance_size OBJC2_UNAVAILABLE; // 該類的實例變量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 該類的成員變量鏈表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定義的鏈表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法緩存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 協(xié)議鏈表
#endif
} OBJC2_UNAVAILABLE;
可以看到運行時一個類還關(guān)聯(lián)了它的超類指針宇驾,類名倍靡,成員變量,方法课舍,緩存塌西,還有附屬的協(xié)議。 其中objc_ivar_list和objc_method_list分別是成員變量列表和方法列表:
struct objc_ivar_list {
int ivar_count
#ifdef __LP64__ int space
#endif
/* variable length structure */
struct objc_ivar ivar_list[1]
}
struct objc_method_list
{
struct objc_method_list *obsolete
int method_count
#ifdef __LP64__
int space
#endif
/* variable length structure */
struct objc_method method_list[1]
}
如果你C語言不是特別好筝尾,可以直接理解為objc_ivar_list結(jié)構(gòu)體存儲著objc_ivar數(shù)組列表捡需,而objc_ivar結(jié)構(gòu)體存儲了類的單個成員變量的信息;同理objc_method_list結(jié)構(gòu)體存儲著objc_method數(shù)組列表筹淫,而objc_method結(jié)構(gòu)體存儲了類的某個方法的信息站辉。最后要提到的還有一個objc_cache,顧名思義它是緩存贸街,它在objc_class的作用很重要庵寞,在后面會講到。不知道你是否注意到了objc_class中也有一個isa對象薛匪,這是因為一個 ObjC 類本身同時也是一個對象捐川,為了處理類和對象的關(guān)系,runtime 庫創(chuàng)建了一種叫做元類 (Meta Class) 的東西逸尖,類對象所屬類型就叫做元類古沥,它用來表述類對象本身所具備的元數(shù)據(jù)。類方法就定義于此處娇跟,因為這些方法可以理解成類對象的實例方法每個類僅有一個類對象岩齿,而每個類對象僅有一個與之相關(guān)的元類。當(dāng)你發(fā)出一個類似[NSObject alloc]的消息時,你事實上是把這個消息發(fā)給了一個類對象 (Class Object) ,這個類對象必須是一個元類的實例臂聋,而這個元類同時也是一個根元類 (root meta class) 的實例涧狮。你會說 NSObject的子類時,你的類就會指向NSObject做為其超類。但是所有的元類最終都指向根元類為其超類。所有的元類的方法列表都有能夠響應(yīng)消息的類方法。所以當(dāng) [NSObject alloc]這條消息發(fā)給類對象的時候肃晚,objc_msgSend()會去它的元類里面去查找能夠響應(yīng)消息的方法,如果找到了仔戈,然后對這個類對象執(zhí)行方法調(diào)用关串。
上圖實線是 super_class 指針拧廊,虛線是isa指針。 有趣的是根元類的超類是NSObject晋修,而isa指向了自己吧碾,而NSObject的超類為nil,也就是它沒有超類飞蚓。
IMP
IMP實際上是一個函數(shù)指針滤港,指向方法實現(xiàn)的首地址。其定義如下:
id (*IMP)(id, SEL, ...)
這個函數(shù)使用當(dāng)前CPU架構(gòu)實現(xiàn)的標(biāo)準(zhǔn)的C調(diào)用約定趴拧。第一個參數(shù)是指向self的指針(如果是實例方法溅漾,則是類實例的內(nèi)存地址;如果是類方法著榴,則是指向元類的指針)添履,第二個參數(shù)是方法選擇器(selector),接下來是方法的實際參數(shù)列表脑又。
前面介紹過的SEL就是為了查找方法的最終實現(xiàn)IMP的暮胧。由于每個方法對應(yīng)唯一的SEL,因此我們可以通過SEL方便快速準(zhǔn)確地獲得它所對應(yīng)的IMP问麸,查找過程將在下面討論往衷。取得IMP后,我們就獲得了執(zhí)行這個方法代碼的入口點严卖,此時席舍,我們就可以像調(diào)用普通的C語言函數(shù)一樣來使用這個函數(shù)指針了。通過取得IMP哮笆,我們可以跳過Runtime的消息傳遞機制来颤,直接執(zhí)行IMP指向的函數(shù)實現(xiàn),這樣省去Runtime消息傳遞過程中所做的一系列查找操作稠肘,會比直接向?qū)ο蟀l(fā)送消息高效一些福铅。
Method
Method是一種代表類中的某個方法的類型
typedef struct objc_method *Method;
而objc_method存儲了方法名,方法類型和方法實現(xiàn):
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
方法名類型為SEL项阴,相同名字的方法即使在不同類中定義滑黔,它們的方法選擇器也相同。方法類型method_types是個char指針环揽,其實存儲著方法的參數(shù)類型和返回值類型拷沸。method_imp指向了方法的實現(xiàn),本質(zhì)上是一個函數(shù)指針薯演。
Ivar
Ivar
Ivar是表示實例變量的類型,其實際是一個指向objc_ivar結(jié)構(gòu)體的指針秧了,其定義如下:
typedef struct objc_ivar *Ivar;
struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE; // 變量名
char *ivar_type OBJC2_UNAVAILABLE; // 變量類型
int ivar_offset OBJC2_UNAVAILABLE; // 基地址偏移字節(jié)
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
Property
@property或者標(biāo)記了類中的屬性,是一個指向objc_property結(jié)構(gòu)體的指針:
typedef struct objc_property *Property;
typedef struct objc_property *objc_property_t;//這個更常用
可以通過class_copyPropertyList和 protocol_copyPropertyList方法來獲取類和協(xié)議中的屬性:
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)
返回類型為指向指針的指針跨扮,哈哈,因為屬性列表是個數(shù)組,每個元素內(nèi)容都是一個objc_property_t指針衡创,而這兩個函數(shù)返回的值是指向這個數(shù)組的指針帝嗡。
Cache
上面提到了objc_class結(jié)構(gòu)體中的cache字段,它用于緩存調(diào)用過的方法璃氢。這個字段是一個指向objc_cache結(jié)構(gòu)體的指針哟玷,其定義如下:
struct objc_cache {
unsigned int mask/* total = mask + 1 */; OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};