Runtime運行時之對象烛缔、類、元類

Objective-C語言是一門動態(tài)語言赠尾,它將很多靜態(tài)語言在編譯和鏈接時期做的事放到了運行時來處理力穗。這種動態(tài)語言的優(yōu)勢在于:我們寫代碼時更具靈活性毅弧,如我們可以把消息轉發(fā)給我們想要的對象气嫁,或者隨意交換一個方法的實現(xiàn)等。

這種特性意味著Objective-C不僅需要一個編譯器够坐,還需要一個運行時系統(tǒng)來執(zhí)行編譯的代碼寸宵。對于Objective-C來說,這個運行時系統(tǒng)就像一個操作系統(tǒng)一樣:它讓所有的工作可以正常的運行元咙。這個運行時系統(tǒng)即Objc Runtime梯影。Objc Runtime其實是一個Runtime庫,它基本上是用C和匯編寫的庶香,這個庫使得C語言有了面向對象的能力甲棍。

Runtime庫主要做下面幾件事:

封裝:在這個庫中,對象可以用C語言中的結構體表示赶掖,而方法可以用C函數(shù)來實現(xiàn)感猛,另外再加上了一些額外的特性七扰。這些結構體和函數(shù)被runtime函數(shù)封裝后,我們就可以在程序運行時創(chuàng)建陪白,檢查颈走,修改類、對象和它們的方法了咱士。

找出方法的最終執(zhí)行代碼:當程序執(zhí)行[object doSomething]時立由,會向消息接收者(object)發(fā)送一條消息(doSomething),runtime會根據(jù)消息接收者是否能響應該消息而做出不同的反應序厉。這將在后面詳細介紹锐膜。

Objective-C runtime目前有兩個版本:Modern runtime和Legacy runtime。Modern Runtime 覆蓋了64位的Mac OS X Apps弛房,還有 iOS Apps枣耀,Legacy Runtime 是早期用來給32位 Mac OS X Apps 用的,也就是可以不用管就是了庭再。

在這一系列文章中捞奕,我們將介紹runtime的基本工作原理,以及如何利用它讓我們的程序變得更加靈活拄轻。在本文中颅围,我們先來介紹一下類與對象,這是面向對象的基礎恨搓,我們看看在Runtime中院促,類是如何實現(xiàn)的。

類與對象基礎數(shù)據(jù)結構

Class

Objective-C類是由Class類型來表示的斧抱,它實際上是一個指向objc_class結構體的指針常拓。它的定義如下:

typedef struct objc_class *Class;

查看objc/runtime.h中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;? // 類的版本信息,默認為0

long info? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;? // 類信息辉浦,供運行期使用的一些位標識

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;

在這個定義中弄抬,下面幾個字段是我們感興趣的

isa:需要注意的是在Objective-C中,所有的類自身也是一個對象宪郊,這個對象的Class里面也有一個isa指針掂恕,它指向metaClass(元類),我們會在后面介紹它弛槐。

super_class:指向該類的父類懊亡,如果該類已經是最頂層的根類(如NSObject或NSProxy),則super_class為NULL乎串。

cache:用于緩存最近使用的方法店枣。一個接收者對象接收到一個消息時,它會根據(jù)isa指針去查找能夠響應這個消息的對象。在實際使用中鸯两,這個對象只有一部分方法是常用的坏瞄,很多方法其實很少用或者根本用不上。這種情況下甩卓,如果每次消息來時鸠匀,我們都是methodLists中遍歷一遍,性能勢必很差逾柿。這時缀棍,cache就派上用場了。在我們每次調用過一個方法后机错,這個方法就會被緩存到cache列表中爬范,下次調用的時候runtime就會優(yōu)先去cache中查找,如果cache沒有弱匪,才去methodLists中查找方法青瀑。這樣,對于那些經常用到的方法的調用萧诫,但提高了調用的效率斥难。

version:我們可以使用這個字段來提供類的版本信息。這對于對象的序列化非常有用帘饶,它可是讓我們識別出不同類定義版本中實例變量布局的改變哑诊。

針對cache,我們用下面例子來說明其執(zhí)行過程:

NSArray *array = [[NSArray alloc] init];

其流程是:

[NSArray alloc]先被執(zhí)行及刻。因為NSArray沒有+alloc方法镀裤,于是去父類NSObject去查找赖舟。

檢測NSObject是否響應+alloc方法喘沿,發(fā)現(xiàn)響應,于是檢測NSArray類萨蚕,并根據(jù)其所需的內存空間大小開始分配內存空間颗搂,然后把isa指針指向NSArray類担猛。同時,+alloc也被加進cache列表里面峭火。

接著毁习,執(zhí)行-init方法智嚷,如果NSArray響應該方法卖丸,則直接將其加入cache;如果不響應盏道,則去父類查找稍浆。

在后期的操作中,如果再以[[NSArray alloc] init]這種方式來創(chuàng)建數(shù)組,則會直接從cache中取出相應的方法衅枫,直接調用嫁艇。

元類(Meta Class)

在上面我們提到,所有的類自身也是一個對象弦撩,我們可以向這個對象發(fā)送消息(即調用類方法)步咪。如:

NSArray *array = [NSArray array];

這個例子中,+array消息發(fā)送給了NSArray類益楼,而這個NSArray也是一個對象猾漫。既然是對象,那么它也是一個objc_object指針感凤,它包含一個指向其類的一個isa指針悯周。那么這些就有一個問題了,這個isa指針指向什么呢陪竿?為了調用+array方法禽翼,這個類的isa指針必須指向一個包含這些類方法的一個objc_class結構體。這就引出了meta-class的概念

meta-class是一個類對象的類族跛。

當我們向一個對象發(fā)送消息時闰挡,runtime會在這個對象所屬的這個類的方法列表中查找方法;而向一個類發(fā)送消息時礁哄,會在這個類的meta-class的方法列表中查找解总。

meta-class之所以重要,是因為它存儲著一個類的所有類方法姐仅。每個類都會有一個單獨的meta-class花枫,因為每個類的類方法基本不可能完全相同。

再深入一下掏膏,meta-class也是一個類劳翰,也可以向它發(fā)送一個消息,那么它的isa又是指向什么呢馒疹?為了不讓這種結構無限延伸下去佳簸,Objective-C的設計者讓所有的meta-class的isa指向基類的meta-class,以此作為它們的所屬類颖变。即生均,任何NSObject繼承體系下的meta-class都使用NSObject的meta-class作為自己的所屬類,而基類的meta-class的isa指針是指向它自己腥刹。這樣就形成了一個完美的閉環(huán)马胧。

通過上面的描述,再加上對objc_class結構體中super_class指針的分析衔峰,我們就可以描繪出類及相應meta-class類的一個繼承體系了佩脊,如下圖所示:


對于NSObject繼承體系來說蛙粘,其實例方法對體系中的所有實例、類和meta-class都是有效的威彰;而類方法對于體系內的所有類和meta-class都是有效的出牧。

講了這么多,我們還是來寫個例子吧:

void TestMetaClass(id self, SEL _cmd) {

NSLog(@"This objcet is %p", self);

NSLog(@"Class is %@, super class is %@", [self class], [self superclass]);

Class currentClass = [self class];

for (int i = 0; i < 4; i++) {

NSLog(@"Following the isa pointer %d times gives %p", i, currentClass);

currentClass = objc_getClass((__bridge void *)currentClass);

}

NSLog(@"NSObject's class is %p", [NSObject class]);

NSLog(@"NSObject's meta class is %p", objc_getClass((__bridge void *)[NSObject class]));

}

#pragma mark -

@implementation Test

- (void)ex_registerClassPair {

Class newClass = objc_allocateClassPair([NSError class], "TestClass", 0);

class_addMethod(newClass, @selector(testMetaClass), (IMP)TestMetaClass, "v@:");

objc_registerClassPair(newClass);

id instance = [[newClass alloc] initWithDomain:@"some domain" code:0 userInfo:nil];

[instance performSelector:@selector(testMetaClass)];

}

@end

這個例子是在運行時創(chuàng)建了一個NSError的子類TestClass歇盼,然后為這個子類添加一個方法testMetaClass舔痕,這個方法的實現(xiàn)是TestMetaClass函數(shù)。

運行后豹缀,打印結果是

2014-10-20 22:57:07.352 mountain[1303:41490] This objcet is 0x7a6e22b0

2014-10-20 22:57:07.353 mountain[1303:41490] Class is TestStringClass, super class is NSError

2014-10-20 22:57:07.353 mountain[1303:41490] Following the isa pointer 0 times gives 0x7a6e21b0

2014-10-20 22:57:07.353 mountain[1303:41490] Following the isa pointer 1 times gives 0x0

2014-10-20 22:57:07.353 mountain[1303:41490] Following the isa pointer 2 times gives 0x0

2014-10-20 22:57:07.353 mountain[1303:41490] Following the isa pointer 3 times gives 0x0

2014-10-20 22:57:07.353 mountain[1303:41490] NSObject's class is 0xe10000

2014-10-20 22:57:07.354 mountain[1303:41490] NSObject's meta class is 0x0

我們在for循環(huán)中赵讯,我們通過objc_getClass來獲取對象的isa,并將其打印出來耿眉,依此一直回溯到NSObject的meta-class边翼。分析打印結果,可以看到最后指針指向的地址是0x0鸣剪,即NSObject的meta-class的類地址组底。

這里需要注意的是:我們在一個類對象調用class方法是無法獲取meta-class,它只是返回類而已筐骇。

以上內容摘自此處

下面是動態(tài)創(chuàng)建類债鸡,添加方法,屬性铛纬,成員變量

動態(tài)創(chuàng)建類厌均,繼承與Peson 添加成員變量


獲取成員變量打印

添加屬性

添加方法

添加屬性的時候對應的值


完整demo地址

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市告唆,隨后出現(xiàn)的幾起案子棺弊,更是在濱河造成了極大的恐慌,老刑警劉巖擒悬,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件模她,死亡現(xiàn)場離奇詭異,居然都是意外死亡懂牧,警方通過查閱死者的電腦和手機侈净,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來僧凤,“玉大人畜侦,你說我怎么就攤上這事∏#” “怎么了旋膳?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長吻氧。 經常有香客問我溺忧,道長咏连,這世上最難降的妖魔是什么盯孙? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任鲁森,我火速辦了婚禮,結果婚禮上振惰,老公的妹妹穿的比我還像新娘歌溉。我一直安慰自己,他們只是感情好骑晶,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布痛垛。 她就那樣靜靜地躺著,像睡著了一般桶蛔。 火紅的嫁衣襯著肌膚如雪匙头。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天仔雷,我揣著相機與錄音蹂析,去河邊找鬼。 笑死碟婆,一個胖子當著我的面吹牛电抚,可吹牛的內容都是我干的。 我是一名探鬼主播竖共,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼蝙叛,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了公给?” 一聲冷哼從身側響起借帘,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎淌铐,沒想到半個月后姻蚓,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡匣沼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年狰挡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片释涛。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡加叁,死狀恐怖,靈堂內的尸體忽然破棺而出唇撬,到底是詐尸還是另有隱情它匕,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布窖认,位于F島的核電站豫柬,受9級特大地震影響告希,放射性物質發(fā)生泄漏。R本人自食惡果不足惜烧给,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一燕偶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧础嫡,春花似錦指么、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至巫财,卻和暖如春盗似,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背平项。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工赫舒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人葵礼。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓号阿,卻偏偏與公主長得像,于是被迫代替她去往敵國和親鸳粉。 傳聞我的和親對象是個殘疾皇子扔涧,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內容