參考文章
清晰理解Objective-C元類
object_getClass(obj)與[obj class]的區(qū)別-源代碼解析
刨根問底Objective-C Runtime
元類
這幾天看了一些runtime底層的一些介紹,感受最深的就是對元類的講解了烫幕。突然有種恍然大悟的感覺芒填,以前我們經(jīng)常說一切皆對象梯捕,在這里體現(xiàn)出來了。
1. 元類是什么
眾所周知Objective-C(以下簡稱OC)中的消息機(jī)制雹姊,消息的接收者可以是一個對象瘟忱,也可以是一個類。那么這兩種情況統(tǒng)一為一種情況不是更方便嗎愉昆?蘋果當(dāng)然早就想到了,這也正是元類的用處麻蹋。蘋果統(tǒng)一把消息接收者作為對象跛溉。等等,這也是說類也是對象扮授?yes芳室,就是這樣。就是說刹勃,OC中所有的類都是一種對象堪侯。由一個類實例化來的對象叫實例對象,這好理解荔仁,那么類作為對象(稱之為類對象)伍宦,又是什么類的對象呢芽死?當(dāng)然也容易猜到,就是元類(MetaClass)”⒅簦現(xiàn)在到給元類下定義的時候了:元類就是類對象所屬的類收奔。所以掌呜,實例是類的實例滓玖,類作為對象又是元類的實例。已經(jīng)說了质蕉,OC中所有的類都是一種對象势篡,所以元類也是對象,那么元類是什么的實例呢模暗?答曰:根元類禁悠,同時根元類是其自身的實例。
上面講到了實例對象兑宇、類對象碍侦、元類對象,有什么區(qū)別隶糕?
實例對象:當(dāng)我們在代碼中new一個實例對象時瓷产,拷貝了實例所屬的類的成員變量,但不拷貝類定義的方法枚驻,調(diào)用實例方法時濒旦,調(diào)用實例的isa指針去尋找方法對應(yīng)的函數(shù)指針。
類對象:是一個功能完整的對象再登,特殊之處在于它們是由程序員定義而在運行時由編譯器創(chuàng)建的尔邓,它沒有自己的實例變量(這里區(qū)別于類的成員變量,它們是屬于實例對象的锉矢,而不是屬于類對象的梯嗽,類方法是屬于類對象自己的),但類對象中存著成員變量和實例方法列表沽损。
元類對象:OC的類方法是使用元類的根本原因慷荔,因為其中存儲著對應(yīng)的類對象調(diào)用的方法即類方法。其他時候都傾向于隱藏元類缠俺,因此真實世界沒有人發(fā)送消息給元類對象显晶。元類的定義和創(chuàng)建看起來都是編譯器自動完成的,無須人為干涉壹士。要獲取一個類的元類磷雇,可使用如下定義的函數(shù):
Class objc_getMetaClass(const char *name); // name為類的名字
此外還有一個獲取對象所屬的類的函數(shù):
Class object_getClass(id obj);
由于類對象是元類的實例,所以當(dāng)傳入的參數(shù)為類名時躏救,返回的就是指向該類所屬元類的指針唯笙。
2. 元類的構(gòu)建機(jī)制
既然所有的類都是對象螟蒸,那么元類又是什么類的對象?這樣下去崩掘,不是子子孫孫無窮盡也七嫌?當(dāng)然不行,OC作為一門編程語言苞慢,當(dāng)然要滿足完備性----既定的各條規(guī)則都要滿足诵原,不能相互矛盾,也不能存在漏洞挽放。
OC作為運行時語言绍赛,上面提到的類與元類在運行時都是objc_class類型。在Objective-C2.0中辑畦,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;
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;
/* Use `Class` instead of `struct objc_class *` */
其中吗蚌,Class定義如下:
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
可以看出,OC中每個類都包含一個isa變量纯出,顯然這里的isa是指向另一個類的指針蚯妇,說白了就是表面這個類是哪個類的實例,以便找到代碼中調(diào)用的本類或父類的類方法暂筝。對于NSObject及其子類箩言,指向的就是它的元類,正如實例中也有個isa指針指向其所屬的類一樣乖杠。而對于元類分扎,每個元類的isa指針都指向根元類。那么根元類的isa指向哪里胧洒?---它自己畏吓。這樣就構(gòu)成了一個封閉的循環(huán),實現(xiàn)了無懈可擊的OC類系統(tǒng)卫漫。這種關(guān)系在下面的圖中有清晰的體現(xiàn)菲饼。
除了isa聲明了實例與所屬類的關(guān)系,還有super_class聲明了類列赎、元類的繼承關(guān)系宏悦。每個類對象都有對應(yīng)的元類,每個類(根類除外)都有一個superclass包吝,同樣每個元類也有一個superclass饼煞,并且子類與子元類、父類與父元類分別在同一個層次诗越。這種關(guān)系借用網(wǎng)上的一張圖來說明砖瞧,一目了然。
注意:根元類的superclass不是nil而是根類嚷狞。對于OC原生的類块促,根元類的父類就是系統(tǒng)的根類NSObject荣堰。但根類不一定是NSObject,因為后面介紹的objc_allocateClassPair函數(shù)也可以創(chuàng)建出一個根類竭翠。
3. 元類的構(gòu)建機(jī)制
上面講到了OC運行時類的定義振坚,這里可以看看對象的定義,重點關(guān)注objc_object 和 id斋扰。
#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
#endif
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id (*IMP)(id, SEL, ...);
#endif
這說明OC中每個對象也都包含一個isa變量渡八,這里的isa,指向?qū)嵗龑ο笏鶎俚念惾焓怠T谶\行時呀狼,[obj aMessage];被轉(zhuǎn)化為objc_msgSend(obj, @selector(aMessage));裂允,這里损离,@selector(aMessage)返回一個SEL數(shù)據(jù)類型,即方法選擇器绝编。SEL主要作用是快速的通過方法名字(aMessage)查找到對應(yīng)方法的函數(shù)指針僻澎,然后調(diào)用其函數(shù)。SEL其本身是一個int型的地址十饥,地址中存放著方法的名字窟勃。在一個類中,每一個方法對應(yīng)著一個SEL逗堵。iOS類中不能存在兩個名稱相同的方法秉氧,即使參數(shù)類型不同,因為SEL是根據(jù)方法名生成蜒秤,相同的方法名稱只能對應(yīng)一個SEL汁咏。
當(dāng)一個消息發(fā)送給任何一個對象,方法的檢查器從對象的isa指針開始作媚,然后是父類攘滩。具體地,在objc_msgSend函數(shù)中纸泡,首先通過obj的isa指針找到obj對應(yīng)的class漂问。在class中,有一塊最近調(diào)用的方法的指針緩存女揭,所以先去cache通過selector查找對應(yīng)的method蚤假,若cache中未找到,再去method list中查找吧兔,若method list中未找到磷仰,則去superClass中查找。若能找到掩驱,則將method加入到cache中芒划,以方便下次查找冬竟,并通過method中的函數(shù)指針跳轉(zhuǎn)到對應(yīng)的函數(shù)中去執(zhí)行。對最后一句不了解的話民逼,請看objc_method定義:
struct objc_method {
??SEL method_name; // 方法名稱
??const char *method_typesE; // 參數(shù)和返回類型的描述字串
??IMP method_imp; // 方法的具體的實現(xiàn)的指針
}
由上看出泵殴,OC中實例方法是通過isa找到object所屬的class,再在class中找到要調(diào)用的method拼苍。此可得出結(jié)論:class即類對象中存儲著實例方法笑诅。實際上,類對象中存儲著類定義的一切:成員變量疮鲫、屬性列表吆你、遵守的協(xié)議等,但不包括類方法俊犯。類方法的定義在哪妇多?就是在元類里面。此外元類中還存在著類的信息(類的版本燕侠,名字)者祖,比如發(fā)送一個類消息[class aMessage];,class中的isa就指向class的元類绢彤,在元類中搜索調(diào)用的類方法七问,搜索層次類似于實例方法的搜索。
4. 元類的應(yīng)用
類對象和元類對象的相關(guān)方法:
- object_getClass跟隨實例的isa指針茫舶,返回此實例所屬的類械巡,對于實例對象(instance)返回的是類(class),對于類(class)則返回的是元類(metaclass)饶氏;
- -class方法對于實例對象(instance)會返回類(class)讥耗,但對于類(class)則不會返回元類(metaclass),而只會返回類本身嚷往,即[@"instance" class]返回的是__NSCFConstantString葛账,而[NSString class]返回的是NSString。
- class_isMetaClass可判斷某類是否為元類皮仁。
- 使用objc_allocateClassPair可在運行時創(chuàng)建新的類與元類對籍琳,使用class_addMethod和class_addIvar可向類中增加方法和實例變量,最后使用objc_registerClassPair注冊后贷祈,就可以使用此類了趋急。這體現(xiàn)了OC作為運行時語言的強大之一:在代碼中動態(tài)創(chuàng)建類并添加方法。
Class newClass = objc_allocateClassPair([NSError class], "RuntimeErrorSubclass", 0);
class_addMethod(newClass, @selector(addedMethod), (IMP)added_method_implementation, "v@:");
void added_method_implementation(id self, SEL __cmd)
{
// do something
}
說明:objc_allocateClassPair函數(shù)的作用是創(chuàng)建一個新類newClass及其元類势誊,三個參數(shù)依次為newClass的父類呜达,newClass的名稱,第三個參數(shù)通常為0粟耻。然后可向newClass中添加變量及方法查近,注意若要添加類方法眉踱,需用objc_getClass(newClass)獲取元類,然后向元類中添加類方法霜威。接下來必須把newClass注冊到運行時系統(tǒng)谈喳,否則系統(tǒng)不能識別這個類。
- 根據(jù)以上理解比較一下object_getClass(obj)和[obj class]的區(qū)別
其實很簡單戈泼,直接看源代碼吧婿禽。
object_getClass(obj)的代碼實現(xiàn):
Class object_getClass(id obj)
{
return _object_getClass(obj);
}
其中_object_getClass(obj)是一個靜態(tài)內(nèi)聯(lián)函數(shù),代碼實現(xiàn)如下:
static inline Class _object_getClass(id obj)
{
#if SUPPORT_TAGGED_POINTERS
if (OBJ_IS_TAGGED_PTR(obj)){
uint8_t slotNumber = ((uint8_t)(uint64_t) obj) & 0x0F;
Class isa = _objc_tagged_isa_table[slotNumber];
return isa;
}
#endif
if (obj) return obj->isa;
else return Nil;
}
簡單的說_object_getClass函數(shù)就是返回對象的isa指針大猛。
[obj class]的代碼實現(xiàn)分為兩種情況扭倾,分別是obj為實例對象和類對象,代碼如下所示:
// 類方法直接返回自身指針
+ (Class)class
{
return self;
}
// 實例方法調(diào)用object_getClass挽绩,返回isa指針
- (Class)class
{
return object_getClass(self);
}
通過以上代碼可以看出膛壹,調(diào)用[obj class],不管obj對實例對象還是類對象琼牧,結(jié)果都是一樣的恢筝。
-
看下這幾個個面試題吧
第一題解析如下:
在調(diào)用[self class]時哀卫,會轉(zhuǎn)化為objc_msgSend函數(shù)巨坊。函數(shù)定義如下:
id objc_msgSend(id self, SEL op, ...)
我們把self做為第一個參數(shù)傳遞進(jìn)去。而在調(diào)用[super class]時此改,會轉(zhuǎn)化為objc_msgSendSuper函數(shù)趾撵。看下函數(shù)定義:
id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
第一個參數(shù)是objc_super這樣一個結(jié)構(gòu)體共啃,其定義如下:
struct objc_super {
__unsafe_unretained id receiver;
__unsafe_unretained Class super_class;
};
結(jié)構(gòu)體有兩個成員占调,第一個成員是receiver,類似于上面的objc_msgSend函數(shù)第一個參數(shù)self移剪。第二個成員是記錄當(dāng)前類的父類是什么究珊。
所以當(dāng)調(diào)用[self class]時,實際先調(diào)用的是objc_msgSend函數(shù)纵苛,第一個參數(shù)是Son當(dāng)前的這個實例剿涮,然后在Son這個類里面去找-(Class)class這個方法,沒有就去父類Father里找攻人,也沒有取试,最后在NSObject類中發(fā)現(xiàn)這個方法。而-(Class)class的實現(xiàn)就是返回self的類別怀吻,故上述輸出結(jié)果為Son瞬浓。
objc Runtime開源代碼對- (Class)class方法的實現(xiàn):
- (Class)class {
return object_getClass(self);
}
而當(dāng)調(diào)用[super class]時,會轉(zhuǎn)換為objc_msgSendSuper函數(shù)蓬坡。第一步先構(gòu)造objc_super結(jié)構(gòu)體猿棉,結(jié)構(gòu)體第一個成員就是self磅叛,第二個成員是(id)class_getSuperclass(objc_getClass("son")),實際該函數(shù)輸出結(jié)果為Father萨赁。第二部是去Father這個類里去找- (Class)class宪躯,沒有,然后去NSObject類去找位迂。找到了访雪,最后內(nèi)部是使用objc_msgSend(objc_super->receiver, @selector(class))去調(diào)用掂林,此時已經(jīng)和[self class]調(diào)用相同臣缀,故上述輸出結(jié)果仍然返回Son。
第二題解析如下:
運行結(jié)果是
2014-11-05 14:45:08.474 Test[9412:721945] 1 0 0 0
對于
BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject calss]];
首先還是看看isKindOfClass的實現(xiàn):
- (BOOL)isKindOfClass: aClass
{
Class cls;
for (cls = isa; cls; cls = cls->superclass)
if (cls == (Class)aClass)
return YES;
return NO;
}
當(dāng)[NSObject class]對象第一次進(jìn)行比較的時候泻帮,得到它的isa為NSObject的Meta Class精置,這個時候NSObject Meta Class 和 NSObject Class不相等。然后取出NSObject的Meta Class的Super class锣杂,這個時候又變成了NSObject Class脂倦,所以相等。
對于
BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject calss]];
先看看isMemberOfClass:的實現(xiàn)吧元莫,
- (BOOL)isMemberOf:aClass
{
return isa == (Class)aClass;
}
當(dāng)前的isa指向NSObject的Meta Class赖阻,所以和NSObject Class不相等。所以輸出結(jié)果為NO踱蠢。
第三題解析如下:
結(jié)果是輸出
2017-07-12 10:20:51.067 test[1038:39336] IMP: - [NSObject(Sark) foo] 2017-07-12 10:20:51.068 test[1038:39336] IMP: - [NSObject(Sark) foo]
注意這里有點蹊蹺的是如果將這個分類寫的一個文件import進(jìn)來會編譯不過的火欧,但是如果直接寫在.m文件,像下面這樣茎截,就是好的苇侵,可以編譯過。
#import "ViewController.h"
@interface NSObject (Sark)
+ (void)foo;
@end
@implementation NSObject (Sark)
- (void)foo
{
NSLog(@"IMP: - [NSObject(Sark) foo]");
}
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[NSObject foo];
[[NSObject new] foo];
}
@end
1)objc runtime加載完后企锌,NSObject的Sark Category被加載榆浓。而NSObject的Sark Category的頭文件+ (void)foo并沒有實質(zhì)參與到工作中,只是給編譯器進(jìn)行靜態(tài)檢查撕攒,所以我們編譯上述代碼會出現(xiàn)警告陡鹃,提示我們沒有實現(xiàn)+ (void)foo方法。而在代碼編譯中打却,它已經(jīng)被注釋掉了杉适。
2)實際被加入到Class的method list的方法是- (void)foo,它是一個實例方法柳击,所以加入到當(dāng)前對象NSObject的方法列表中猿推,而不是NSObject Meta class的方法列表中。
3)當(dāng)執(zhí)行[NSObject foo]時,我們看下整個objc_msgSend的過程:
objc_msgSend第一個參數(shù)是"(id)objc_getClass("NSObject")"蹬叭,獲得NSObject Class的對象藕咏。
類方法在Meta Class的方法列表中找,我們在load Category方法時加入的是- (void)foo實例方法秽五,所以并不在NSObject Meta Class的方法列表中孽查,繼續(xù)往super class中找,NSObject Meta Class的super class是NSObject本身坦喘,所以盲再,這個時候我們能夠找到 - (void)foo這個方法。所以輸出結(jié)果瓣铣。
當(dāng)執(zhí)行[[NSObject new] foo]答朋,我們看下整個objc_msgSend的過程:
[NSObject new]生成一個NSObject對象,直接在該對象的類(NSObject)的方法列表里找棠笑,能夠找到梦碗,所以正常輸出結(jié)果。