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 添加成員變量
獲取成員變量打印
添加屬性
添加方法
添加屬性的時候對應的值