Objective-C語言是一門動態(tài)語言,它將很多靜態(tài)語言在編譯和鏈接時期做的事放到了運(yùn)行時來處理。這種動態(tài)語言的優(yōu)勢在于:我們寫代碼時更具靈活性,如我們可以把消息轉(zhuǎn)發(fā)給我們想要的對象,或者隨意交換一個方法的實現(xiàn)等。
這種特性意味著Objective-C不僅需要一個編譯器,還需要一個運(yùn)行時系統(tǒng)來執(zhí)行編譯的代碼。對于Objective-C來說,這個運(yùn)行時系統(tǒng)就像一個操作系統(tǒng)一樣:它讓所有的工作可以正常的運(yùn)行。這個運(yùn)行時系統(tǒng)即Objc Runtime。Objc Runtime其實是一個Runtime庫,它基本上是用C和匯編寫的,這個庫使得C語言有了面向?qū)ο蟮哪芰Α?/p>
Runtime庫主要做下面幾件事:
封裝:在這個庫中,對象可以用C語言中的結(jié)構(gòu)體表示,而方法可以用C函數(shù)來實現(xiàn),另外再加上了一些額外的特性。這些結(jié)構(gòu)體和函數(shù)被runtime函數(shù)封裝后,我們就可以在程序運(yùn)行時創(chuàng)建,檢查,修改類、對象和它們的方法了。
找出方法的最終執(zhí)行代碼:當(dāng)程序執(zhí)行[object doSomething]時,會向消息接收者(object)發(fā)送一條消息(doSomething),runtime會根據(jù)消息接收者是否能響應(yīng)該消息而做出不同的反應(yīng)。這將在后面詳細(xì)介紹。
Objective-C runtime目前有兩個版本:Modern runtime和Legacy runtime。Modern Runtime覆蓋了64位的Mac OS X Apps,還有iOS Apps,Legacy Runtime是早期用來給32位?Mac OS X Apps?用的,也就是可以不用管就是了。
在這一系列文章中,我們將介紹runtime的基本工作原理,以及如何利用它讓我們的程序變得更加靈活。在本文中,我們先來介紹一下類與對象,這是面向?qū)ο蟮幕A(chǔ),我們看看在Runtime中,類是如何實現(xiàn)的。
類與對象基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)
Objective-C類是由Class類型來表示的,它實際上是一個指向objc_class結(jié)構(gòu)體的指針。它的定義如下:
1typedefstructobjc_class *Class;
查看objc/runtime.h中objc_class結(jié)構(gòu)體的定義如下:
structobjc_class {
? ? Class isa? OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;// 父類
constchar*name? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;// 類名
longversion? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;// 類的版本信息,默認(rèn)為0
longinfo? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;// 類信息,供運(yùn)行期使用的一些位標(biāo)識
longinstance_size? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;// 該類的實例變量大小
structobjc_ivar_list *ivars? ? ? ? OBJC2_UNAVAILABLE;// 該類的成員變量鏈表
structobjc_method_list **methodLists OBJC2_UNAVAILABLE;// 方法定義的鏈表
structobjc_cache *cache? ? ? ? ? ? ? OBJC2_UNAVAILABLE;// 方法緩存
structobjc_protocol_list *protocols OBJC2_UNAVAILABLE;// 協(xié)議鏈表
#endif
} OBJC2_UNAVAILABLE;
在這個定義中,下面幾個字段是我們感興趣的
isa:需要注意的是在Objective-C中,所有的類自身也是一個對象,這個對象的Class里面也有一個isa指針,它指向metaClass(元類),我們會在后面介紹它。
super_class:指向該類的父類,如果該類已經(jīng)是最頂層的根類(如NSObject或NSProxy),則super_class為NULL。
cache:用于緩存最近使用的方法。一個接收者對象接收到一個消息時,它會根據(jù)isa指針去查找能夠響應(yīng)這個消息的對象。在實際使用中,這個對象只有一部分方法是常用的,很多方法其實很少用或者根本用不上。這種情況下,如果每次消息來時,我們都是methodLists中遍歷一遍,性能勢必很差。這時,cache就派上用場了。在我們每次調(diào)用過一個方法后,這個方法就會被緩存到cache列表中,下次調(diào)用的時候runtime就會優(yōu)先去cache中查找,如果cache沒有,才去methodLists中查找方法。這樣,對于那些經(jīng)常用到的方法的調(diào)用,但提高了調(diào)用的效率。
version:我們可以使用這個字段來提供類的版本信息。這對于對象的序列化非常有用,它可是讓我們識別出不同類定義版本中實例變量布局的改變。
針對cache,我們用下面例子來說明其執(zhí)行過程:
NSArray*array = [[NSArrayalloc] init];
```
其流程是:
1.`[NSArrayalloc]`先被執(zhí)行。因為NSArray沒有`+alloc`方法,于是去父類NSObject去查找。
2.檢測NSObject是否響應(yīng)`+alloc`方法,發(fā)現(xiàn)響應(yīng),于是檢測NSArray類,并根據(jù)其所需的內(nèi)存空間大小開始分配內(nèi)存空間,然后把`isa`指針指向NSArray類。同時,`+alloc`也被加進(jìn)cache列表里面。
3.接著,執(zhí)行`-init`方法,如果NSArray響應(yīng)該方法,則直接將其加入`cache`;如果不響應(yīng),則去父類查找。
4.在后期的操作中,如果再以`[[NSArrayalloc] init]`這種方式來創(chuàng)建數(shù)組,則會直接從cache中取出相應(yīng)的方法,直接調(diào)用。
### objc_object與id
`objc_object`是表示一個類的實例的結(jié)構(gòu)體,它的定義如下(`objc/objc.h`):
```objc
structobjc_object {
? ? Class isa? OBJC_ISA_AVAILABILITY;
};
typedefstructobjc_object *id;
可以看到,這個結(jié)構(gòu)體只有一個字體,即指向其類的isa指針。這樣,當(dāng)我們向一個Objective-C對象發(fā)送消息時,運(yùn)行時庫會根據(jù)實例對象的isa指針找到這個實例對象所屬的類。Runtime庫會在類的方法列表及父類的方法列表中去尋找與消息對應(yīng)的selector指向的方法。找到后即運(yùn)行這個方法。
當(dāng)創(chuàng)建一個特定類的實例對象時,分配的內(nèi)存包含一個objc_object數(shù)據(jù)結(jié)構(gòu),然后是類的實例變量的數(shù)據(jù)。NSObject類的alloc和allocWithZone:方法使用函數(shù)class_createInstance來創(chuàng)建objc_object數(shù)據(jù)結(jié)構(gòu)。
另外還有我們常見的id,它是一個objc_object結(jié)構(gòu)類型的指針。它的存在可以讓我們實現(xiàn)類似于C++中泛型的一些操作。該類型的對象可以轉(zhuǎn)換為任何一種對象,有點(diǎn)類似于C語言中void *指針類型的作用。
上面提到了objc_class結(jié)構(gòu)體中的cache字段,它用于緩存調(diào)用過的方法。這個字段是一個指向objc_cache結(jié)構(gòu)體的指針,其定義如下:
structobjc_cache {
unsignedintmask/* total = mask + 1 */OBJC2_UNAVAILABLE;
unsignedintoccupied? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
Method buckets[1]? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
};
該結(jié)構(gòu)體的字段描述如下:
mask:一個整數(shù),指定分配的緩存bucket的總數(shù)。在方法查找過程中,Objective-C runtime使用這個字段來確定開始線性查找數(shù)組的索引位置。指向方法selector的指針與該字段做一個AND位操作(index = (mask & selector))。這可以作為一個簡單的hash散列算法。
occupied:一個整數(shù),指定實際占用的緩存bucket的總數(shù)。
buckets:指向Method數(shù)據(jù)結(jié)構(gòu)指針的數(shù)組。這個數(shù)組可能包含不超過mask+1個元素。需要注意的是,指針可能是NULL,表示這個緩存bucket沒有被占用,另外被占用的bucket可能是不連續(xù)的。這個數(shù)組可能會隨著時間而增長。
在上面我們提到,所有的類自身也是一個對象,我們可以向這個對象發(fā)送消息(即調(diào)用類方法)。如:
1NSArray*array = [NSArrayarray];
這個例子中,+array消息發(fā)送給了NSArray類,而這個NSArray也是一個對象。既然是對象,那么它也是一個objc_object指針,它包含一個指向其類的一個isa指針。那么這些就有一個問題了,這個isa指針指向什么呢?為了調(diào)用+array方法,這個類的isa指針必須指向一個包含這些類方法的一個objc_class結(jié)構(gòu)體。這就引出了meta-class的概念
meta-class是一個類對象的類。
當(dāng)我們向一個對象發(fā)送消息時,runtime會在這個對象所屬的這個類的方法列表中查找方法;而向一個類發(fā)送消息時,會在這個類的meta-class的方法列表中查找。
meta-class之所以重要,是因為它存儲著一個類的所有類方法。每個類都會有一個單獨(dú)的meta-class,因為每個類的類方法基本不可能完全相同。
再深入一下,meta-class也是一個類,也可以向它發(fā)送一個消息,那么它的isa又是指向什么呢?為了不讓這種結(jié)構(gòu)無限延伸下去,Objective-C的設(shè)計者讓所有的meta-class的isa指向基類的meta-class,以此作為它們的所屬類。即,任何NSObject繼承體系下的meta-class都使用NSObject的meta-class作為自己的所屬類,而基類的meta-class的isa指針是指向它自己。這樣就形成了一個完美的閉環(huán)。
通過上面的描述,再加上對objc_class結(jié)構(gòu)體中super_class指針的分析,我們就可以描繪出類及相應(yīng)meta-class類的一個繼承體系了,如下圖所示:
對于NSObject繼承體系來說,其實例方法對體系中的所有實例、類和meta-class都是有效的;而類方法對于體系內(nèi)的所有類和meta-class都是有效的。
講了這么多,我們還是來寫個例子吧:
1
2
3
4
5
6
7
8
9
10
11
12
13
estMetaClass(idself, SEL _cmd) {
NSLog(@"This objcet is %p",self);
NSLog(@"Class is %@, super class is %@", [selfclass], [selfsuperclass]);
Class currentClass = [selfclass];
for(inti =0; i <4; i++) {
NSLog(@"Following the isa pointer %d times gives %p", i, currentClass);
currentClass = objc_getClass((__bridgevoid*)currentClass);
? ? }
NSLog(@"NSObject's class is %p", [NSObjectclass]);
NSLog(@"NSObject's meta class is %p", objc_getClass((__bridgevoid*)[NSObjectclass]));
}
#pragma mark -
@implementationTest
- (void)ex_registerClassPair {
Class newClass = objc_allocateClassPair([NSErrorclass],"TestClass",0);
class_addMethod(newClass,@selector(testMetaClass), (IMP)TestMetaClass,"v@:");
? ? objc_registerClassPair(newClass);
idinstance = [[newClass alloc] initWithDomain:@"some domain"code:0userInfo:nil];
[instance performSelector:@selector(testMetaClass)];
}
@end
這個例子是在運(yùn)行時創(chuàng)建了一個NSError的子類TestClass,然后為這個子類添加一個方法testMetaClass,這個方法的實現(xiàn)是TestMetaClass函數(shù)。
運(yùn)行后,打印結(jié)果是
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。分析打印結(jié)果,可以看到最后指針指向的地址是0x0,即NSObject的meta-class的類地址。
這里需要注意的是:我們在一個類對象調(diào)用class方法是無法獲取meta-class,它只是返回類而已。
runtime提供了大量的函數(shù)來操作類與對象。類的操作方法大部分是以class_為前綴的,而對象的操作方法大部分是以objc_或object_為前綴。下面我們將根據(jù)這些方法的用途來分類討論這些方法的使用。
我們可以回過頭去看看objc_class的定義,runtime提供的操作類的方法主要就是針對這個結(jié)構(gòu)體中的各個字段的。下面我們分別介紹這一些的函數(shù)。并在最后以實例來演示這些函數(shù)的具體用法。
類名操作的函數(shù)主要有:
1
2
// 獲取類的類名
constchar* class_getName ( Class cls );
對于class_getName函數(shù),如果傳入的cls為Nil,則返回一個字字符串。
父類(super_class)和元類(meta-class)
父類和元類操作的函數(shù)主要有:
1
2
3
4
5
// 獲取類的父類
Class class_getSuperclass ( Class cls );
// 判斷給定的Class是否是一個元類
BOOLclass_isMetaClass ( Class cls );
class_getSuperclass函數(shù),當(dāng)cls為Nil或者cls為根類時,返回Nil。不過通常我們可以使用NSObject類的superclass方法來達(dá)到同樣的目的。
class_isMetaClass函數(shù),如果是cls是元類,則返回YES;如果否或者傳入的cls為Nil,則返回NO。
實例變量大小操作的函數(shù)有:
1
2
// 獲取實例大小
size_t class_getInstanceSize ( Class cls );
在objc_class中,所有的成員變量、屬性的信息是放在鏈表ivars中的。ivars是一個數(shù)組,數(shù)組中每個元素是指向Ivar(變量信息)的指針。runtime提供了豐富的函數(shù)來操作這一字段。大體上可以分為以下幾類:
1.成員變量操作函數(shù),主要包含以下函數(shù):
1
2
3
4
5
6
7
8
9
10
11
// 獲取類中指定名稱實例成員變量的信息
Ivar class_getInstanceVariable ( Class cls,constchar*name );
// 獲取類成員變量的信息
Ivar class_getClassVariable ( Class cls,constchar*name );
// 添加成員變量
BOOLclass_addIvar ( Class cls,constchar*name, size_t size, uint8_t alignment,constchar*types );
// 獲取整個成員變量列表
Ivar * class_copyIvarList ( Class cls,unsignedint*outCount );
class_getInstanceVariable函數(shù),它返回一個指向包含name指定的成員變量信息的objc_ivar結(jié)構(gòu)體的指針(Ivar)。
class_getClassVariable函數(shù),目前沒有找到關(guān)于Objective-C中類變量的信息,一般認(rèn)為Objective-C不支持類變量。注意,返回的列表不包含父類的成員變量和屬性。
Objective-C不支持往已存在的類中添加實例變量,因此不管是系統(tǒng)庫提供的提供的類,還是我們自定義的類,都無法動態(tài)添加成員變量。但如果我們通過運(yùn)行時來創(chuàng)建一個類的話,又應(yīng)該如何給它添加成員變量呢?這時我們就可以使用class_addIvar函數(shù)了。不過需要注意的是,這個方法只能在objc_allocateClassPair函數(shù)與objc_registerClassPair之間調(diào)用。另外,這個類也不能是元類。成員變量的按字節(jié)最小對齊量是1<<alignment。這取決于ivar的類型和機(jī)器的架構(gòu)。如果變量的類型是指針類型,則傳遞log2(sizeof(pointer_type))。
class_copyIvarList函數(shù),它返回一個指向成員變量信息的數(shù)組,數(shù)組中每個元素是指向該成員變量信息的objc_ivar結(jié)構(gòu)體的指針。這個數(shù)組不包含在父類中聲明的變量。outCount指針返回數(shù)組的大小。需要注意的是,我們必須使用free()來釋放這個數(shù)組。
2.屬性操作函數(shù),主要包含以下函數(shù):
1
2
3
4
5
6
7
8
9
10
11
// 獲取指定的屬性
objc_property_t class_getProperty ( Class cls,constchar*name );
// 獲取屬性列表
objc_property_t * class_copyPropertyList ( Class cls,unsignedint*outCount );
// 為類添加屬性
BOOLclass_addProperty ( Class cls,constchar*name,constobjc_property_attribute_t *attributes,unsignedintattributeCount );
// 替換類的屬性
voidclass_replaceProperty ( Class cls,constchar*name,constobjc_property_attribute_t *attributes,unsignedintattributeCount );
這一種方法也是針對ivars來操作,不過只操作那些是屬性的值。我們在后面介紹屬性時會再遇到這些函數(shù)。
3.在MAC OS X系統(tǒng)中,我們可以使用垃圾回收器。runtime提供了幾個函數(shù)來確定一個對象的內(nèi)存區(qū)域是否可以被垃圾回收器掃描,以處理strong/weak引用。這幾個函數(shù)定義如下:
1
2
3
4
constuint8_t * class_getIvarLayout ( Class cls );
voidclass_setIvarLayout ( Class cls,constuint8_t *layout );
constuint8_t * class_getWeakIvarLayout ( Class cls );
voidclass_setWeakIvarLayout ( Class cls,constuint8_t *layout );
但通常情況下,我們不需要去主動調(diào)用這些方法;在調(diào)用objc_registerClassPair時,會生成合理的布局。在此不詳細(xì)介紹這些函數(shù)。
方法操作主要有以下函數(shù):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 添加方法
BOOLclass_addMethod ( Class cls, SEL name, IMP imp,constchar*types );
// 獲取實例方法
Method class_getInstanceMethod ( Class cls, SEL name );
// 獲取類方法
Method class_getClassMethod ( Class cls, SEL name );
// 獲取所有方法的數(shù)組
Method * class_copyMethodList ( Class cls,unsignedint*outCount );
// 替代方法的實現(xiàn)
IMP class_replaceMethod ( Class cls, SEL name, IMP imp,constchar*types );
// 返回方法的具體實現(xiàn)
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );
// 類實例是否響應(yīng)指定的selector
BOOLclass_respondsToSelector ( Class cls, SEL sel );
class_addMethod的實現(xiàn)會覆蓋父類的方法實現(xiàn),但不會取代本類中已存在的實現(xiàn),如果本類中包含一個同名的實現(xiàn),則函數(shù)會返回NO。如果要修改已存在實現(xiàn),可以使用method_setImplementation。一個Objective-C方法是一個簡單的C函數(shù),它至少包含兩個參數(shù)–self和_cmd。所以,我們的實現(xiàn)函數(shù)(IMP參數(shù)指向的函數(shù))至少需要兩個參數(shù),如下所示:
1
2
3
4
voidmyMethodIMP(idself, SEL _cmd)
{
// implementation ....
}
與成員變量不同的是,我們可以為類動態(tài)添加方法,不管這個類是否已存在。
另外,參數(shù)types是一個描述傳遞給方法的參數(shù)類型的字符數(shù)組,這就涉及到類型編碼,我們將在后面介紹。
class_getInstanceMethod、class_getClassMethod函數(shù),與class_copyMethodList不同的是,這兩個函數(shù)都會去搜索父類的實現(xiàn)。
class_copyMethodList函數(shù),返回包含所有實例方法的數(shù)組,如果需要獲取類方法,則可以使用class_copyMethodList(object_getClass(cls), &count)(一個類的實例方法是定義在元類里面)。該列表不包含父類實現(xiàn)的方法。outCount參數(shù)返回方法的個數(shù)。在獲取到列表后,我們需要使用free()方法來釋放它。
class_replaceMethod函數(shù),該函數(shù)的行為可以分為兩種:如果類中不存在name指定的方法,則類似于class_addMethod函數(shù)一樣會添加方法;如果類中已存在name指定的方法,則類似于method_setImplementation一樣替代原方法的實現(xiàn)。
class_getMethodImplementation函數(shù),該函數(shù)在向類實例發(fā)送消息時會被調(diào)用,并返回一個指向方法實現(xiàn)函數(shù)的指針。這個函數(shù)會比method_getImplementation(class_getInstanceMethod(cls, name))更快。返回的函數(shù)指針可能是一個指向runtime內(nèi)部的函數(shù),而不一定是方法的實際實現(xiàn)。例如,如果類實例無法響應(yīng)selector,則返回的函數(shù)指針將是運(yùn)行時消息轉(zhuǎn)發(fā)機(jī)制的一部分。
class_respondsToSelector函數(shù),我們通常使用NSObject類的respondsToSelector:或instancesRespondToSelector:方法來達(dá)到相同目的。
協(xié)議相關(guān)的操作包含以下函數(shù):
1
2
3
4
5
6
7
8
// 添加協(xié)議
BOOLclass_addProtocol ( Class cls, Protocol *protocol );
// 返回類是否實現(xiàn)指定的協(xié)議
BOOLclass_conformsToProtocol ( Class cls, Protocol *protocol );
// 返回類實現(xiàn)的協(xié)議列表
Protocol * class_copyProtocolList ( Class cls,unsignedint*outCount );
class_conformsToProtocol函數(shù)可以使用NSObject類的conformsToProtocol:方法來替代。
class_copyProtocolList函數(shù)返回的是一個數(shù)組,在使用后我們需要使用free()手動釋放。
版本相關(guān)的操作包含以下函數(shù):
1
2
3
4
5
// 獲取版本號
intclass_getVersion ( Class cls );
// 設(shè)置版本號
voidclass_setVersion ( Class cls,intversion );
runtime還提供了兩個函數(shù)來供CoreFoundation的tool-free bridging使用,即:
1
2
Class objc_getFutureClass (constchar*name );
voidobjc_setFutureClass ( Class cls,constchar*name );
通常我們不直接使用這兩個函數(shù)。
上面列舉了大量類操作的函數(shù),下面我們寫個實例,來看看這些函數(shù)的實例效果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
//-----------------------------------------------------------
// MyClass.h
@interfaceMyClass:NSObject
@property(nonatomic,strong)NSArray*array;
@property(nonatomic,copy)NSString*string;
- (void)method1;
- (void)method2;
+ (void)classMethod1;
@end
//-----------------------------------------------------------
// MyClass.m
#import"MyClass.h"
@interfaceMyClass(){
NSInteger_instance1;
NSString*? _instance2;
}
@property(nonatomic,assign)NSUIntegerinteger;
- (void)method3WithArg1:(NSInteger)arg1 arg2:(NSString*)arg2;
@end
@implementationMyClass
+ (void)classMethod1 {
}
- (void)method1 {
NSLog(@"call method method1");
}
- (void)method2 {
}
- (void)method3WithArg1:(NSInteger)arg1 arg2:(NSString*)arg2 {
NSLog(@"arg1 : %ld, arg2 : %@", arg1, arg2);
}
@end
//-----------------------------------------------------------
// main.h
#import"MyClass.h"
#import"MySubClass.h"
#import<objc/runtime.h>
intmain(intargc,constchar* argv[]) {
@autoreleasepool{
? ? ? ? MyClass *myClass = [[MyClass alloc] init];
unsignedintoutCount =0;
? ? ? ? Class cls = myClass.class;
// 類名
NSLog(@"class name: %s", class_getName(cls));
NSLog(@"==========================================================");
// 父類
NSLog(@"super class name: %s", class_getName(class_getSuperclass(cls)));
NSLog(@"==========================================================");
// 是否是元類
NSLog(@"MyClass is %@ a meta-class", (class_isMetaClass(cls) ?@"":@"not"));
NSLog(@"==========================================================");
? ? ? ? Class meta_class = objc_getMetaClass(class_getName(cls));
NSLog(@"%s's meta-class is %s", class_getName(cls), class_getName(meta_class));
NSLog(@"==========================================================");
// 變量實例大小
NSLog(@"instance size: %zu", class_getInstanceSize(cls));
NSLog(@"==========================================================");
// 成員變量
? ? ? ? Ivar *ivars = class_copyIvarList(cls, &outCount);
for(inti =0; i < outCount; i++) {
? ? ? ? ? ? Ivar ivar = ivars[i];
NSLog(@"instance variable's name: %s at index: %d", ivar_getName(ivar), i);
? ? ? ? }
? ? ? ? free(ivars);
Ivar string = class_getInstanceVariable(cls,"_string");
if(string !=NULL) {
NSLog(@"instace variable %s", ivar_getName(string));
? ? ? ? }
NSLog(@"==========================================================");
// 屬性操作
? ? ? ? objc_property_t * properties = class_copyPropertyList(cls, &outCount);
for(inti =0; i < outCount; i++) {
? ? ? ? ? ? objc_property_t property = properties[i];
NSLog(@"property's name: %s", property_getName(property));
? ? ? ? }
? ? ? ? free(properties);
objc_property_t array = class_getProperty(cls,"array");
if(array !=NULL) {
NSLog(@"property %s", property_getName(array));
? ? ? ? }
NSLog(@"==========================================================");
// 方法操作
? ? ? ? Method *methods = class_copyMethodList(cls, &outCount);
for(inti =0; i < outCount; i++) {
? ? ? ? ? ? Method method = methods[i];
NSLog(@"method's signature: %s", method_getName(method));
? ? ? ? }
? ? ? ? free(methods);
Method method1 = class_getInstanceMethod(cls,@selector(method1));
if(method1 !=NULL) {
NSLog(@"method %s", method_getName(method1));
? ? ? ? }
Method classMethod = class_getClassMethod(cls,@selector(classMethod1));
if(classMethod !=NULL) {
NSLog(@"class method : %s", method_getName(classMethod));
? ? ? ? }
NSLog(@"MyClass is%@ responsd to selector: method3WithArg1:arg2:", class_respondsToSelector(cls,@selector(method3WithArg1:arg2:)) ?@"":@" not");
IMP imp = class_getMethodImplementation(cls,@selector(method1));
? ? ? ? imp();
NSLog(@"==========================================================");
// 協(xié)議
Protocol * __unsafe_unretained* protocols = class_copyProtocolList(cls, &outCount);
? ? ? ? Protocol * protocol;
for(inti =0; i < outCount; i++) {
? ? ? ? ? ? protocol = protocols[i];
NSLog(@"protocol name: %s", protocol_getName(protocol));
? ? ? ? }
NSLog(@"MyClass is%@ responsed to protocol %s", class_conformsToProtocol(cls, protocol) ?@"":@" not", protocol_getName(protocol));
NSLog(@"==========================================================");
? ? }
return0;
}
這段程序的輸出如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
2014-10-22 19:41:37.452 RuntimeTest[3189:156810] class name: MyClass
2014-10-22 19:41:37.453 RuntimeTest[3189:156810] ==========================================================
2014-10-22 19:41:37.454 RuntimeTest[3189:156810] super class name: NSObject
2014-10-22 19:41:37.454 RuntimeTest[3189:156810] ==========================================================
2014-10-22 19:41:37.454 RuntimeTest[3189:156810] MyClass is not a meta-class
2014-10-22 19:41:37.454 RuntimeTest[3189:156810] ==========================================================
2014-10-22 19:41:37.454 RuntimeTest[3189:156810] MyClass's meta-class is MyClass
2014-10-22 19:41:37.455 RuntimeTest[3189:156810] ==========================================================
2014-10-22 19:41:37.455 RuntimeTest[3189:156810] instance size: 48
2014-10-22 19:41:37.455 RuntimeTest[3189:156810] ==========================================================
2014-10-22 19:41:37.455 RuntimeTest[3189:156810] instance variable's name: _instance1 at index: 0
2014-10-22 19:41:37.455 RuntimeTest[3189:156810] instance variable's name: _instance2 at index: 1
2014-10-22 19:41:37.455 RuntimeTest[3189:156810] instance variable's name: _array at index: 2
2014-10-22 19:41:37.455 RuntimeTest[3189:156810] instance variable's name: _string at index: 3
2014-10-22 19:41:37.463 RuntimeTest[3189:156810] instance variable's name: _integer at index: 4
2014-10-22 19:41:37.463 RuntimeTest[3189:156810] instace variable _string
2014-10-22 19:41:37.463 RuntimeTest[3189:156810] ==========================================================
2014-10-22 19:41:37.463 RuntimeTest[3189:156810] property's name: array
2014-10-22 19:41:37.463 RuntimeTest[3189:156810] property's name: string
2014-10-22 19:41:37.464 RuntimeTest[3189:156810] property's name: integer
2014-10-22 19:41:37.464 RuntimeTest[3189:156810] property array
2014-10-22 19:41:37.464 RuntimeTest[3189:156810] ==========================================================
2014-10-22 19:41:37.464 RuntimeTest[3189:156810] method's signature: method1
2014-10-22 19:41:37.464 RuntimeTest[3189:156810] method's signature: method2
2014-10-22 19:41:37.464 RuntimeTest[3189:156810] method's signature: method3WithArg1:arg2:
2014-10-22 19:41:37.465 RuntimeTest[3189:156810] method's signature: integer
2014-10-22 19:41:37.465 RuntimeTest[3189:156810] method's signature: setInteger:
2014-10-22 19:41:37.465 RuntimeTest[3189:156810] method's signature: array
2014-10-22 19:41:37.465 RuntimeTest[3189:156810] method's signature: string
2014-10-22 19:41:37.465 RuntimeTest[3189:156810] method's signature: setString:
2014-10-22 19:41:37.465 RuntimeTest[3189:156810] method's signature: setArray:
2014-10-22 19:41:37.466 RuntimeTest[3189:156810] method's signature: .cxx_destruct
2014-10-22 19:41:37.466 RuntimeTest[3189:156810] method method1
2014-10-22 19:41:37.466 RuntimeTest[3189:156810] class method : classMethod1
2014-10-22 19:41:37.466 RuntimeTest[3189:156810] MyClass is responsd to selector: method3WithArg1:arg2:
2014-10-22 19:41:37.467 RuntimeTest[3189:156810] call method method1
2014-10-22 19:41:37.467 RuntimeTest[3189:156810] ==========================================================
2014-10-22 19:41:37.467 RuntimeTest[3189:156810] protocol name: NSCopying
2014-10-22 19:41:37.467 RuntimeTest[3189:156810] protocol name: NSCoding
2014-10-22 19:41:37.467 RuntimeTest[3189:156810] MyClass is responsed to protocol NSCoding
2014-10-22 19:41:37.468 RuntimeTest[3189:156810] ==========================================================
runtime的強(qiáng)大之處在于它能在運(yùn)行時創(chuàng)建類和對象。
動態(tài)創(chuàng)建類涉及到以下幾個函數(shù):
1
2
3
4
5
6
7
8
9
// 創(chuàng)建一個新類和元類
Class objc_allocateClassPair ( Class superclass,constchar*name, size_t extraBytes );
// 銷毀一個類及其相關(guān)聯(lián)的類
voidobjc_disposeClassPair ( Class cls );
// 在應(yīng)用中注冊由objc_allocateClassPair創(chuàng)建的類
voidobjc_registerClassPair ( Class cls );
objc_allocateClassPair函數(shù):如果我們要創(chuàng)建一個根類,則superclass指定為Nil。extraBytes通常指定為0,該參數(shù)是分配給類和元類對象尾部的索引ivars的字節(jié)數(shù)。
為了創(chuàng)建一個新類,我們需要調(diào)用objc_allocateClassPair。然后使用諸如class_addMethod,class_addIvar等函數(shù)來為新創(chuàng)建的類添加方法、實例變量和屬性等。完成這些后,我們需要調(diào)用objc_registerClassPair函數(shù)來注冊類,之后這個新類就可以在程序中使用了。
實例方法和實例變量應(yīng)該添加到類自身上,而類方法應(yīng)該添加到類的元類上。
objc_disposeClassPair函數(shù)用于銷毀一個類,不過需要注意的是,如果程序運(yùn)行中還存在類或其子類的實例,則不能調(diào)用針對類調(diào)用該方法。
在前面介紹元類時,我們已經(jīng)有接觸到這幾個函數(shù)了,在此我們再舉個實例來看看這幾個函數(shù)的使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Class cls = objc_allocateClassPair(MyClass.class,"MySubClass",0);
class_addMethod(cls,@selector(submethod1), (IMP)imp_submethod1,"v@:");
class_replaceMethod(cls,@selector(method1), (IMP)imp_submethod1,"v@:");
class_addIvar(cls,"_ivar1",sizeof(NSString*), log(sizeof(NSString*)),"i");
objc_property_attribute_t type = {"T","@\"NSString\""};
objc_property_attribute_t ownership = {"C",""};
objc_property_attribute_t backingivar = {"V","_ivar1"};
objc_property_attribute_t attrs[] = {type, ownership, backingivar};
class_addProperty(cls,"property2", attrs,3);
objc_registerClassPair(cls);
idinstance = [[cls alloc] init];
[instance performSelector:@selector(submethod1)];
[instance performSelector:@selector(method1)];
程序的輸出如下:
1
2
2014-10-2311:35:31.006RuntimeTest[3800:66152] run sub method1
2014-10-2311:35:31.006RuntimeTest[3800:66152] run sub method1
動態(tài)創(chuàng)建對象的函數(shù)如下:
1
2
3
4
5
6
7
8
// 創(chuàng)建類實例
idclass_createInstance ( Class cls, size_t extraBytes );
// 在指定位置創(chuàng)建類實例
idobjc_constructInstance ( Class cls,void*bytes );
// 銷毀類實例
void* objc_destructInstance (idobj );
class_createInstance函數(shù):創(chuàng)建實例時,會在默認(rèn)的內(nèi)存區(qū)域為類分配內(nèi)存。extraBytes參數(shù)表示分配的額外字節(jié)數(shù)。這些額外的字節(jié)可用于存儲在類定義中所定義的實例變量之外的實例變量。該函數(shù)在ARC環(huán)境下無法使用。
調(diào)用class_createInstance的效果與+alloc方法類似。不過在使用class_createInstance時,我們需要確切的知道我們要用它來做什么。在下面的例子中,我們用NSString來測試一下該函數(shù)的實際效果: