Objective-C是一門(mén)動(dòng)態(tài)語(yǔ)言紧显,它將很多靜態(tài)語(yǔ)言在編譯和鏈接時(shí)期做的事放到了運(yùn)行時(shí)來(lái)處理撇他。這樣處理也就意味著逛裤,它將使我們的代碼更有靈活性指孤。比如启涯,我們可以根據(jù)我們的意向?qū)⑾⑥D(zhuǎn)發(fā)給其它對(duì)象,或者去替換我們想要實(shí)現(xiàn)的方法等恃轩。因?yàn)镺bjective-C“動(dòng)態(tài)化”的內(nèi)容都是在運(yùn)行時(shí)完成的结洼,所以,OC的運(yùn)行條件不僅僅要求有幫助我們向機(jī)器說(shuō)話的編譯器叉跛,還要有讓代碼隨心而動(dòng)的運(yùn)行時(shí)松忍,而這個(gè)運(yùn)行時(shí)就是objc Runtime。它基本上是由C和匯編實(shí)現(xiàn)的筷厘,它讓C具有了面向?qū)ο蟮哪芰?/strong>。
Runtime庫(kù)主要做下面幾件事:
- 封裝:在這個(gè)庫(kù)中酥艳,對(duì)象可以用C語(yǔ)言中的結(jié)構(gòu)體表示摊溶,而方法可以用C函數(shù)來(lái)實(shí)現(xiàn),另外再加上了一些額外的特性玖雁。這些結(jié)構(gòu)體和函數(shù)被runtime函數(shù)封裝后更扁,我們就可以在程序運(yùn)行時(shí)創(chuàng)建,檢查,修改類浓镜、對(duì)象和它們的方法了溃列。
- 找出方法的最終執(zhí)行代碼:當(dāng)程序執(zhí)行[object doSomething]時(shí),會(huì)向消息接收者(object)發(fā)送一條消息(doSomething)膛薛,runtime會(huì)根據(jù)消息接收者是否能響應(yīng)該消息而做出不同的反應(yīng)听隐。
類與對(duì)象
Objective-C是對(duì)C的進(jìn)一步封裝,讓C有了面對(duì)對(duì)象的能力哄啄,為什么這么說(shuō)呢雅任,我們可以看一下下面的這個(gè)例子:
Person *per = [[Person alloc] init];
這是一個(gè)很簡(jiǎn)單的獲取實(shí)例化對(duì)象的方法。
那么這個(gè)語(yǔ)句在經(jīng)過(guò)編譯后會(huì)變成什么樣呢咨跌,想看的話我們可以這樣做:
打開(kāi)Xcode沪么,創(chuàng)建一個(gè)命令行文件。
創(chuàng)建一個(gè).m 文件锌半,在里邊定義我們想要的類禽车, 內(nèi)容如下所示:
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, assign) NSInteger age;
@end
@implementation Person
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
p.age = 18;
NSLog(@"%ld", p.age);
}
return 0;
}
- 打開(kāi)命令行,編譯main.m為.cpp文件刊殉,打開(kāi)文件可以看到main函數(shù)中的實(shí)現(xiàn)如下:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */
{
__AtAutoreleasePool __autoreleasepool;
Person *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, NSInteger))(void *)objc_msgSend)((id)p, sel_registerName("setAge:"), (NSInteger)18);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_dd_c58dp2w556l44jqlhxdgthdh0000gn_T_main_449954_mi_0, ((NSInteger (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("age")));
}
return 0;
}
在以上函數(shù)中殉摔,采用了消息發(fā)送機(jī)制,可以看到记焊,OC在這里也脫去了它面向?qū)ο蟮耐庖乱菰拢@露除了些許本質(zhì)。
以實(shí)例化對(duì)象p的初始化過(guò)程為例遍膜,簡(jiǎn)要分析一下其實(shí)現(xiàn)過(guò)程:
首先碗硬,在原文件中的
Person *p = [[Person alloc]init];
先后調(diào)用了兩個(gè)方法,也即發(fā)送了兩個(gè)消息瓢颅, 對(duì)應(yīng)的肛响,編譯后文件就是實(shí)現(xiàn)這一過(guò)程。
Person *(*)(id, SEL)(void *)objc_msgSend((id)objc_getClass("Person"), sel_registerName("alloc"));
這一方法對(duì)應(yīng)的就是Person調(diào)用的alloc方法惜索,objc_msgSend函數(shù)發(fā)送alloc消息(需要強(qiáng)轉(zhuǎn)),它將會(huì)返回一個(gè)結(jié)構(gòu)體(Person的實(shí)例化對(duì)象)剃浇。同理巾兆,返回的對(duì)象在執(zhí)行初始化方法init時(shí),需要再次發(fā)送消息虎囚,也就有了在.cpp文件中我們看到的樣子:
Person *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
*注:在.m中如果也想如此實(shí)現(xiàn)角塑,需要引用<objc/runtime.h>和<objc/message.h>
數(shù)據(jù)結(jié)構(gòu)
Class
Objective-C類是由Class類型來(lái)表示的,它實(shí)際上是一個(gè)指向objc_class結(jié)構(gòu)體的指針淘讥。
查看objc/runtime.h中objc_class結(jié)構(gòu)體的定義如下:
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; // 類信息,供運(yùn)行期使用的一些位標(biāo)識(shí)
long instance_size OBJC2_UNAVAILABLE; // 該類的實(shí)例變量大小
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;
MetaClass
在objc_class的結(jié)構(gòu)體中有isa這么一個(gè)字段,在Objective-C中窒朋,所有的類自身也是一個(gè)對(duì)象搀罢,這個(gè)對(duì)象的Class里面也有一個(gè)isa指針,它指向metaClass(元類)侥猩。
當(dāng)我們向一個(gè)對(duì)象發(fā)送消息時(shí)榔至,runtime會(huì)在這個(gè)對(duì)象所屬的這個(gè)類的方法列表中查找方法;而向一個(gè)類發(fā)送消息時(shí)欺劳,會(huì)在這個(gè)類的meta-class的方法列表中查找唧取。
meta-class之所以重要,是因?yàn)樗鎯?chǔ)著一個(gè)類的所有類方法划提。每個(gè)類都會(huì)有一個(gè)單獨(dú)的meta-class枫弟,因?yàn)槊總€(gè)類的類方法基本不可能完全相同。
再深入一下鹏往,meta-class也是一個(gè)類淡诗,也可以向它發(fā)送一個(gè)消息,那么它的isa又是指向什么呢掸犬?為了不讓這種結(jié)構(gòu)無(wú)限延伸下去袜漩,Objective-C的設(shè)計(jì)者讓所有的meta-class的isa指向基類的meta-class,以此作為它們的所屬類湾碎。即宙攻,任何NSObject繼承體系下的meta-class都使用NSObject的meta-class作為自己的所屬類,而基類的meta-class的isa指針是指向它自己介褥。這樣就形成了一個(gè)完美的閉環(huán)座掘。
通過(guò)上面的描述,再加上對(duì)objc_class結(jié)構(gòu)體中super_class指針的分析柔滔,我們就可以描繪出類及相應(yīng)meta-class類的一個(gè)繼承體系了溢陪,如下圖所示:
我是這樣去理解這個(gè)圖的:
圖中的各個(gè)類的關(guān)系以及isa指向就如同一個(gè)老亞家的家譜以及財(cái)產(chǎn)關(guān)系,首先圖中的subClass可以看做是以諾睛廊, 他管理著一個(gè)村落中的所有兵器(instance of Class的方法)形真,當(dāng)村民需要使用時(shí),需要問(wèn)自己的村長(zhǎng)有沒(méi)有超全,并在有的情況下調(diào)用咆霜,如果沒(méi)有呢?當(dāng)你要去干掉一條龍嘶朱,意氣風(fēng)發(fā)的去跟村長(zhǎng)要?jiǎng)r(shí)蛾坯,發(fā)現(xiàn)他竟然沒(méi)有,當(dāng)時(shí)你就要崩潰疏遏,村長(zhǎng)一看脉课,“哎喲喲救军,你可別死我家門(mén)口,我問(wèn)問(wèn)我老爹有沒(méi)有”倘零,然后就給該隱打電話唱遭,向他借,再不行就讓該隱向亞當(dāng)借视事,這要是都沒(méi)有胆萧。。以諾就會(huì)告訴村民“行了俐东,大家一起死吧跌穗,你要的東西大家都沒(méi)有”。然后全世界就崩潰掉了(野指針:指向了未識(shí)別的方法)虏辫。
這時(shí)候你就要問(wèn)了蚌吸,那subclass(meta)又是何方神圣呢?好吧砌庄,我也不知道她叫啥羹唠,是以諾的媳婦就是了,她藏著以諾的小金庫(kù)娄昆,里邊裝著村長(zhǎng)能用的兵器(類方法)佩微,當(dāng)村長(zhǎng)要用時(shí)就得向她申請(qǐng),同理萌焰,她沒(méi)有就得向她媽要哺眯,在這里因?yàn)榕訜o(wú)需上戰(zhàn)場(chǎng),所以她們本身要兵器扒俯。奶卓。你猜她們想干嘛,所以她們想要兵器直接向RootClass(meta)夏娃說(shuō)句悄悄話撼玄, 夏娃想要兵器也沒(méi)地要去夺姑,看看自己有啥吧。秉承著男傳男掌猛,女傳女的原則盏浙,可以很清楚的看清圖中的繼承關(guān)系。等等荔茬,天吶只盹,RootClass(meta)繼承于RootClass,這是咋回事兔院?嘿嘿,別忘了站削,夏娃是用亞當(dāng)?shù)囊桓吖窃斐鰜?lái)的坊萝。而亞當(dāng),你能跟造出他那位打通電話嗎?