前幾天看到老哥說他去面試散劫,面試官問他OC里類對(duì)象和實(shí)例對(duì)象有什么區(qū)別涨缚?不知道Objective-C對(duì)象模型的同學(xué)很可能搞不清楚面試官究竟想問什么蝙场。
本篇先講對(duì)象模型到底是什么樣的以及runtime中巷送,類是如何實(shí)現(xiàn)的衩匣。
Objective-C對(duì)象模型
我們打開<objc/objc.h>文件可以看到如下對(duì)NSObject的定義:
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
可以看到NSObject 就是一個(gè)包含 isa 指針的結(jié)構(gòu)體未檩,在 Objective-C 語言中戴尸,每一個(gè)類實(shí)際上也是一個(gè)對(duì)象。每一個(gè)類也有一個(gè)名為 isa 的指針冤狡。每一個(gè)類也可以接受消息孙蒙,例如[NSObject alloc]项棠,就是向 NSObject 這個(gè)類發(fā)送名為alloc消息。
再看一下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;
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;
#endif
} OBJC2_UNAVAILABLE;
發(fā)現(xiàn)他里面也有一個(gè)isa指針挎峦,所以類也是一個(gè)對(duì)象香追,那它也必須是另一個(gè)類的實(shí)列,這個(gè)類就是元類 (metaclass)坦胶。元類保存了類方法的列表透典。當(dāng)一個(gè)類方法被調(diào)用時(shí),元類會(huì)首先查找它本身是否有該類方法的實(shí)現(xiàn)顿苇,如果沒有峭咒,則該元類會(huì)向它的父類查找該方法,直到一直找到繼承鏈的頭纪岁。
1.isa:需要注意的是在Objective-C中凑队,所有的類自身也是一個(gè)對(duì)象,這個(gè)對(duì)象的Class里面也有一個(gè)isa指針幔翰,它指向metaClass(元類)漩氨,我們會(huì)在后面介紹它。
2.super_class:指向該類的父類遗增,如果該類已經(jīng)是最頂層的根類(如NSObject或NSProxy)叫惊,則super_class為NULL。
3.cache:用于緩存最近使用的方法做修。一個(gè)接收者對(duì)象接收到一個(gè)消息時(shí)赋访,它會(huì)根據(jù)isa指針去查找能夠響應(yīng)這個(gè)消息的對(duì)象。在實(shí)際使用中缓待,這個(gè)對(duì)象只有一部分方法是常用的蚓耽,很多方法其實(shí)很少用或者根本用不上。這種情況下旋炒,如果每次消息來時(shí)步悠,我們都是methodLists中遍歷一遍,性能勢(shì)必很差瘫镇。這時(shí)鼎兽,cache就派上用場(chǎng)了。在我們每次調(diào)用過一個(gè)方法后铣除,這個(gè)方法就會(huì)被緩存到cache列表中谚咬,下次調(diào)用的時(shí)候runtime就會(huì)優(yōu)先去cache中查找,如果cache沒有尚粘,才去methodLists中查找方法择卦。這樣,對(duì)于那些經(jīng)常用到的方法的調(diào)用,但提高了調(diào)用的效率秉继。
4.version:我們可以使用這個(gè)字段來提供類的版本信息祈噪。這對(duì)于對(duì)象的序列化非常有用,它可是讓我們識(shí)別出不同類定義版本中實(shí)例變量布局的改變尚辑。
元類 (metaclass)
元類 (metaclass) 也是一個(gè)對(duì)象辑鲤,那么元類的 isa 指針又指向哪里呢?為了設(shè)計(jì)上的完整杠茬,所有的元類的 isa 指針都會(huì)指向一個(gè)根元類 (root metaclass)月褥。根元類 (root metaclass) 本身的 isa 指針指向自己,這樣就行成了一個(gè)閉環(huán)瓢喉。
我們?cè)賮砜纯蠢^承關(guān)系宁赤,由于類方法的定義是保存在元類 (metaclass) 中,而方法調(diào)用的規(guī)則是灯荧,如果該類沒有一個(gè)方法的實(shí)現(xiàn),則向它的父類繼續(xù)查找盐杂。所以逗载,為了保證父類的類方法可以在子類中可以被調(diào)用,所以子類的元類會(huì)繼承父類的元類链烈,換而言之厉斟,類對(duì)象和元類對(duì)象有著同樣的繼承關(guān)系。如下圖:
圖中的Root Class 是指 NSObject强衡,我們可以從圖中看出:
1.NSObject 類包括它的對(duì)象實(shí)例方法擦秽。
2.NSObject 的元類包括它的類方法,例如 alloc 方法漩勤。
3.NSObject 的元類繼承自 NSObject 類感挥。
4.一個(gè) NSObject 的類中的方法同時(shí)也會(huì)被 NSObject 的子類在查找方法時(shí)找到
為了驗(yàn)證上面說的內(nèi)容,我做了以下幾個(gè)測(cè)試越败,在這之前需要先了解幾個(gè)函數(shù)具體的作用與實(shí)現(xiàn)方式触幼。
+class 與 objc_getClass()
1.調(diào)用+class方法是無法獲取meta-class,它只是返回類本身究飞。
2.對(duì)類對(duì)象調(diào)用objc_getClass()可以獲取它的meta-class置谦。
-isKindOfClass和isMemberOfClass
+ (BOOL) isKindOfClass:(Class)class
{
Class r0 = object_getClass(self);
while (1) {
if (r0 == 0) {
return 0;
}else{
if (r0 != class) {
r0 = [r0 superclass];
}else{
return 1;
}
}
}
}
+ (BOOL)isMemberOfClass:(Class)class
{
return isa == (Class)aClass;
}
它們的內(nèi)部實(shí)現(xiàn)大概是這個(gè)樣子∫诟担可以看出:
isKindOfClass:確定一個(gè)對(duì)象是否是一個(gè)類的成員,或者是派生自該類的成員.
isMemberOfClass:確定一個(gè)對(duì)象是否是當(dāng)前類的成員
驗(yàn)證例子1
BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
輸出結(jié)果:YES/NO/NO/NO媒峡。
1.[NSObject class]獲取的NSObject類對(duì)象 在函數(shù)體內(nèi)獲取的是它的元類,而NSObject的元類的父類就是NSObject本身葵擎,所以第一個(gè)是YES谅阿。
2.因?yàn)槭莍sMemberOfClass,所以元類不等于自己,返回NO
3.同上奔穿,獲得的是元類镜沽,這里元類的父類與類對(duì)象不相等。
4.同上贱田。
類的成員變量
如果把類的實(shí)例看成一個(gè) C 語言的結(jié)構(gòu)體(struct)缅茉,上面說的 isa 指針就是這個(gè)結(jié)構(gòu)體的第一個(gè)成員變量,而類的其它成員變量依次排列在結(jié)構(gòu)體中男摧。
驗(yàn)證例子2
@interface NSObject (test)
+ (void)foo;
- (void)foo
@end
@implementation NSObject (test)
- (void)foo {
NSLog(@"test result");
}
@end
// 測(cè)試代碼
[NSObject foo];
[[NSObject new] foo];
測(cè)試結(jié)果:都正常的運(yùn)行了-foo方法蔬墩。
原因是NSObject的類方法保存在根元類中,根元類沒有這個(gè)方法便向它的父類尋找耗拓,根元類的父類指向NSObject本身拇颅,而成員方法是保存在類中的,所以找到了這個(gè)方法乔询。
可變與不可變
因?yàn)閷?duì)象在內(nèi)存中的排布可以看成一個(gè)結(jié)構(gòu)體樟插,該結(jié)構(gòu)體的大小并不能動(dòng)態(tài)變化。所以無法在運(yùn)行時(shí)動(dòng)態(tài)給對(duì)象增加成員變量竿刁。
相對(duì)的黄锤,對(duì)象的方法定義都保存在類的可變區(qū)域中。我們可以看到方法的定義列表是一個(gè)名為 methodLists的指針的指針(如下圖所示)食拜。通過修改該指針指向的指針的值鸵熟,就可以實(shí)現(xiàn)動(dòng)態(tài)地為某一個(gè)類增加成員方法。
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
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;
#endif
} OBJC2_UNAVAILABLE;
Category
Category是表示一個(gè)指向分類的結(jié)構(gòu)體的指針负甸,其定義如下:
typedef struct objc_category *Category
struct objc_category{
char *category_name OBJC2_UNAVAILABLE; // 分類名
char *class_name OBJC2_UNAVAILABLE; // 分類所屬的類名
struct objc_method_list *instance_methods OBJC2_UNAVAILABLE; // 實(shí)例方法列表
struct objc_method_list *class_methods OBJC2_UNAVAILABLE; // 類方法列表
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 分類所實(shí)現(xiàn)的協(xié)議列表
}
這個(gè)結(jié)構(gòu)體主要包含了分類定義的實(shí)例方法與類方法流强,其中instance_methods列表是objc_class中方法列表的一個(gè)子集,而class_methods列表是元類方法列表的一個(gè)子集呻待。
可發(fā)現(xiàn)打月,類別中沒有ivar成員變量指針,也就意味著:類別中不能夠添加實(shí)例變量和屬性
小結(jié):
1.實(shí)例對(duì)象是類的對(duì)象蚕捉,類對(duì)象是元類的對(duì)象僵控。
2.類對(duì)象和元類對(duì)象有著同樣的繼承關(guān)系
3.成員方法保存在類中,類方法保存在元類中鱼冀。
- Category包含對(duì)象方法列表和元類方法列表报破,但沒有ivar成員變量指針。所以只能添加方法不能添加變量和屬性千绪。
5.調(diào)用類方法時(shí)充易,是先查找元類本身是否有該方法,如果沒有荸型,則元類向它的父類查找盹靴,而不是類對(duì)象的父類。
相關(guān)文章
神經(jīng)病院objc runtime入院考試
Objective-C對(duì)象模型及應(yīng)用
Objective-C Runtime 運(yùn)行時(shí)之一:類與對(duì)象
初識(shí) Objective-C Runtime
Runtime之類與對(duì)象總結(jié)