Objective-C Runtime 運(yùn)行時(shí)之一:類與對象

Objective-C語言是一門動(dòng)態(tài)語言锯蛀,它將很多靜態(tài)語言在編譯和鏈接時(shí)期做的事放到了運(yùn)行時(shí)來處理扭仁。這種動(dòng)態(tài)語言的優(yōu)勢在于:我們寫代碼時(shí)更具靈活性致稀,如我們可以把消息轉(zhuǎn)發(fā)給我們想要的對象,或者隨意交換一個(gè)方法的實(shí)現(xiàn)等钻弄。

這種特性意味著Objective-C不僅需要一個(gè)編譯器,還需要一個(gè)運(yùn)行時(shí)系統(tǒng)來執(zhí)行編譯的代碼者吁。對于Objective-C來說窘俺,這個(gè)運(yùn)行時(shí)系統(tǒng)就像一個(gè)操作系統(tǒng)一樣:它讓所有的工作可以正常的運(yùn)行。這個(gè)運(yùn)行時(shí)系統(tǒng)即Objc Runtime复凳。Objc Runtime其實(shí)是一個(gè)Runtime庫瘤泪,它基本上是用C和匯編寫的,這個(gè)庫使得C語言有了面向?qū)ο蟮哪芰Α?/p>

Runtime庫主要做下面幾件事:

封裝:在這個(gè)庫中育八,對象可以用C語言中的結(jié)構(gòu)體表示对途,而方法可以用C函數(shù)來實(shí)現(xiàn),另外再加上了一些額外的特性髓棋。這些結(jié)構(gòu)體和函數(shù)被runtime函數(shù)封裝后实檀,我們就可以在程序運(yùn)行時(shí)創(chuàng)建,檢查按声,修改類膳犹、對象和它們的方法了。

找出方法的最終執(zhí)行代碼:當(dāng)程序執(zhí)行[object doSomething]時(shí)签则,會(huì)向消息接收者(object)發(fā)送一條消息(doSomething)须床,runtime會(huì)根據(jù)消息接收者是否能響應(yīng)該消息而做出不同的反應(yīng)。這將在后面詳細(xì)介紹渐裂。

Objective-C runtime目前有兩個(gè)版本: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中计盒,類是如何實(shí)現(xiàn)的渴频。

類與對象基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)

Class

Objective-C類是由Class類型來表示的,它實(shí)際上是一個(gè)指向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)識(shí)

longinstance_size? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;// 該類的實(shí)例變量大小

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;

在這個(gè)定義中,下面幾個(gè)字段是我們感興趣的

isa:需要注意的是在Objective-C中场钉,所有的類自身也是一個(gè)對象蚊俺,這個(gè)對象的Class里面也有一個(gè)isa指針,它指向metaClass(元類)逛万,我們會(huì)在后面介紹它泳猬。

super_class:指向該類的父類,如果該類已經(jīng)是最頂層的根類(如NSObject或NSProxy)宇植,則super_class為NULL得封。

cache:用于緩存最近使用的方法。一個(gè)接收者對象接收到一個(gè)消息時(shí)指郁,它會(huì)根據(jù)isa指針去查找能夠響應(yīng)這個(gè)消息的對象忙上。在實(shí)際使用中,這個(gè)對象只有一部分方法是常用的闲坎,很多方法其實(shí)很少用或者根本用不上疫粥。這種情況下,如果每次消息來時(shí)箫柳,我們都是methodLists中遍歷一遍手形,性能勢必很差。這時(shí)悯恍,cache就派上用場了库糠。在我們每次調(diào)用過一個(gè)方法后,這個(gè)方法就會(huì)被緩存到cache列表中涮毫,下次調(diào)用的時(shí)候runtime就會(huì)優(yōu)先去cache中查找瞬欧,如果cache沒有,才去methodLists中查找方法罢防。這樣艘虎,對于那些經(jīng)常用到的方法的調(diào)用,但提高了調(diào)用的效率咒吐。

version:我們可以使用這個(gè)字段來提供類的版本信息野建。這對于對象的序列化非常有用,它可是讓我們識(shí)別出不同類定義版本中實(shí)例變量布局的改變恬叹。

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

NSArray*array = [[NSArrayalloc] init];

```

其流程是:

1.`[NSArrayalloc]`先被執(zhí)行。因?yàn)镹SArray沒有`+alloc`方法绽昼,于是去父類NSObject去查找唯鸭。

2.檢測NSObject是否響應(yīng)`+alloc`方法,發(fā)現(xiàn)響應(yīng)硅确,于是檢測NSArray類目溉,并根據(jù)其所需的內(nèi)存空間大小開始分配內(nèi)存空間明肮,然后把`isa`指針指向NSArray類。同時(shí)缭付,`+alloc`也被加進(jìn)cache列表里面柿估。

3.接著,執(zhí)行`-init`方法蛉腌,如果NSArray響應(yīng)該方法官份,則直接將其加入`cache`;如果不響應(yīng)烙丛,則去父類查找。

4.在后期的操作中羔味,如果再以`[[NSArrayalloc] init]`這種方式來創(chuàng)建數(shù)組河咽,則會(huì)直接從cache中取出相應(yīng)的方法,直接調(diào)用赋元。

### objc_object與id

`objc_object`是表示一個(gè)類的實(shí)例的結(jié)構(gòu)體忘蟹,它的定義如下(`objc/objc.h`):

```objc

structobjc_object {

? ? Class isa? OBJC_ISA_AVAILABILITY;

};

typedefstructobjc_object *id;

可以看到,這個(gè)結(jié)構(gòu)體只有一個(gè)字體搁凸,即指向其類的isa指針媚值。這樣,當(dāng)我們向一個(gè)Objective-C對象發(fā)送消息時(shí)护糖,運(yùn)行時(shí)庫會(huì)根據(jù)實(shí)例對象的isa指針找到這個(gè)實(shí)例對象所屬的類褥芒。Runtime庫會(huì)在類的方法列表及父類的方法列表中去尋找與消息對應(yīng)的selector指向的方法。找到后即運(yùn)行這個(gè)方法嫡良。

當(dāng)創(chuàng)建一個(gè)特定類的實(shí)例對象時(shí)锰扶,分配的內(nèi)存包含一個(gè)objc_object數(shù)據(jù)結(jié)構(gòu),然后是類的實(shí)例變量的數(shù)據(jù)寝受。NSObject類的alloc和allocWithZone:方法使用函數(shù)class_createInstance來創(chuàng)建objc_object數(shù)據(jù)結(jié)構(gòu)坷牛。

另外還有我們常見的id,它是一個(gè)objc_object結(jié)構(gòu)類型的指針很澄。它的存在可以讓我們實(shí)現(xiàn)類似于C++中泛型的一些操作京闰。該類型的對象可以轉(zhuǎn)換為任何一種對象,有點(diǎn)類似于C語言中void *指針類型的作用甩苛。

objc_cache

上面提到了objc_class結(jié)構(gòu)體中的cache字段蹂楣,它用于緩存調(diào)用過的方法。這個(gè)字段是一個(gè)指向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:一個(gè)整數(shù)捐迫,指定分配的緩存bucket的總數(shù)。在方法查找過程中爱葵,Objective-C runtime使用這個(gè)字段來確定開始線性查找數(shù)組的索引位置施戴。指向方法selector的指針與該字段做一個(gè)AND位操作(index = (mask & selector))反浓。這可以作為一個(gè)簡單的hash散列算法。

occupied:一個(gè)整數(shù)赞哗,指定實(shí)際占用的緩存bucket的總數(shù)雷则。

buckets:指向Method數(shù)據(jù)結(jié)構(gòu)指針的數(shù)組。這個(gè)數(shù)組可能包含不超過mask+1個(gè)元素肪笋。需要注意的是月劈,指針可能是NULL,表示這個(gè)緩存bucket沒有被占用藤乙,另外被占用的bucket可能是不連續(xù)的猜揪。這個(gè)數(shù)組可能會(huì)隨著時(shí)間而增長。

元類(Meta Class)

在上面我們提到坛梁,所有的類自身也是一個(gè)對象而姐,我們可以向這個(gè)對象發(fā)送消息(即調(diào)用類方法)。如:

1NSArray*array = [NSArrayarray];

這個(gè)例子中划咐,+array消息發(fā)送給了NSArray類拴念,而這個(gè)NSArray也是一個(gè)對象。既然是對象褐缠,那么它也是一個(gè)objc_object指針政鼠,它包含一個(gè)指向其類的一個(gè)isa指針。那么這些就有一個(gè)問題了队魏,這個(gè)isa指針指向什么呢公般?為了調(diào)用+array方法,這個(gè)類的isa指針必須指向一個(gè)包含這些類方法的一個(gè)objc_class結(jié)構(gòu)體器躏。這就引出了meta-class的概念

meta-class是一個(gè)類對象的類俐载。

當(dāng)我們向一個(gè)對象發(fā)送消息時(shí),runtime會(huì)在這個(gè)對象所屬的這個(gè)類的方法列表中查找方法登失;而向一個(gè)類發(fā)送消息時(shí)遏佣,會(huì)在這個(gè)類的meta-class的方法列表中查找。

meta-class之所以重要揽浙,是因?yàn)樗鎯?chǔ)著一個(gè)類的所有類方法状婶。每個(gè)類都會(huì)有一個(gè)單獨(dú)的meta-class,因?yàn)槊總€(gè)類的類方法基本不可能完全相同馅巷。

再深入一下膛虫,meta-class也是一個(gè)類,也可以向它發(fā)送一個(gè)消息钓猬,那么它的isa又是指向什么呢稍刀?為了不讓這種結(jié)構(gòu)無限延伸下去,Objective-C的設(shè)計(jì)者讓所有的meta-class的isa指向基類的meta-class,以此作為它們的所屬類账月。即综膀,任何NSObject繼承體系下的meta-class都使用NSObject的meta-class作為自己的所屬類,而基類的meta-class的isa指針是指向它自己局齿。這樣就形成了一個(gè)完美的閉環(huán)剧劝。

通過上面的描述,再加上對objc_class結(jié)構(gòu)體中super_class指針的分析抓歼,我們就可以描繪出類及相應(yīng)meta-class類的一個(gè)繼承體系了讥此,如下圖所示:

對于NSObject繼承體系來說,其實(shí)例方法對體系中的所有實(shí)例谣妻、類和meta-class都是有效的萄喳;而類方法對于體系內(nèi)的所有類和meta-class都是有效的。

講了這么多蹋半,我們還是來寫個(gè)例子吧:

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

這個(gè)例子是在運(yùn)行時(shí)創(chuàng)建了一個(gè)NSError的子類TestClass取胎,然后為這個(gè)子類添加一個(gè)方法testMetaClass,這個(gè)方法的實(shí)現(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的類地址手蝎。

這里需要注意的是:我們在一個(gè)類對象調(diào)用class方法是無法獲取meta-class榕莺,它只是返回類而已。

類與對象操作函數(shù)

runtime提供了大量的函數(shù)來操作類與對象棵介。類的操作方法大部分是以class_為前綴的钉鸯,而對象的操作方法大部分是以objc_或object_為前綴。下面我們將根據(jù)這些方法的用途來分類討論這些方法的使用邮辽。

類相關(guān)操作函數(shù)

我們可以回過頭去看看objc_class的定義唠雕,runtime提供的操作類的方法主要就是針對這個(gè)結(jié)構(gòu)體中的各個(gè)字段的。下面我們分別介紹這一些的函數(shù)吨述。并在最后以實(shí)例來演示這些函數(shù)的具體用法岩睁。

類名(name)

類名操作的函數(shù)主要有:

1

2

// 獲取類的類名

constchar* class_getName ( Class cls );

對于class_getName函數(shù),如果傳入的cls為Nil揣云,則返回一個(gè)字字符串捕儒。

父類(super_class)和元類(meta-class)

父類和元類操作的函數(shù)主要有:

1

2

3

4

5

// 獲取類的父類

Class class_getSuperclass ( Class cls );

// 判斷給定的Class是否是一個(gè)元類

BOOLclass_isMetaClass ( Class cls );

class_getSuperclass函數(shù),當(dāng)cls為Nil或者cls為根類時(shí)邓夕,返回Nil刘莹。不過通常我們可以使用NSObject類的superclass方法來達(dá)到同樣的目的阎毅。

class_isMetaClass函數(shù)喘落,如果是cls是元類奋献,則返回YES烤低;如果否或者傳入的cls為Nil蒋畜,則返回NO悦荒。

實(shí)例變量大小(instance_size)

實(shí)例變量大小操作的函數(shù)有:

1

2

// 獲取實(shí)例大小

size_t class_getInstanceSize ( Class cls );

成員變量(ivars)及屬性

在objc_class中驰徊,所有的成員變量垛贤、屬性的信息是放在鏈表ivars中的真朗。ivars是一個(gè)數(shù)組雌团,數(shù)組中每個(gè)元素是指向Ivar(變量信息)的指針燃领。runtime提供了豐富的函數(shù)來操作這一字段。大體上可以分為以下幾類:

1.成員變量操作函數(shù)锦援,主要包含以下函數(shù):

1

2

3

4

5

6

7

8

9

10

11

// 獲取類中指定名稱實(shí)例成員變量的信息

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 );

// 獲取整個(gè)成員變量列表

Ivar * class_copyIvarList ( Class cls,unsignedint*outCount );

class_getInstanceVariable函數(shù)猛蔽,它返回一個(gè)指向包含name指定的成員變量信息的objc_ivar結(jié)構(gòu)體的指針(Ivar)。

class_getClassVariable函數(shù)灵寺,目前沒有找到關(guān)于Objective-C中類變量的信息曼库,一般認(rèn)為Objective-C不支持類變量。注意略板,返回的列表不包含父類的成員變量和屬性毁枯。

Objective-C不支持往已存在的類中添加實(shí)例變量,因此不管是系統(tǒng)庫提供的提供的類叮称,還是我們自定義的類种玛,都無法動(dòng)態(tài)添加成員變量。但如果我們通過運(yùn)行時(shí)來創(chuàng)建一個(gè)類的話瓤檐,又應(yīng)該如何給它添加成員變量呢赂韵?這時(shí)我們就可以使用class_addIvar函數(shù)了。不過需要注意的是挠蛉,這個(gè)方法只能在objc_allocateClassPair函數(shù)與objc_registerClassPair之間調(diào)用祭示。另外,這個(gè)類也不能是元類碌秸。成員變量的按字節(jié)最小對齊量是1<<alignment绍移。這取決于ivar的類型和機(jī)器的架構(gòu)。如果變量的類型是指針類型讥电,則傳遞log2(sizeof(pointer_type))蹂窖。

class_copyIvarList函數(shù),它返回一個(gè)指向成員變量信息的數(shù)組恩敌,數(shù)組中每個(gè)元素是指向該成員變量信息的objc_ivar結(jié)構(gòu)體的指針瞬测。這個(gè)數(shù)組不包含在父類中聲明的變量。outCount指針返回?cái)?shù)組的大小。需要注意的是月趟,我們必須使用free()來釋放這個(gè)數(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í)會(huì)再遇到這些函數(shù)。

3.在MAC OS X系統(tǒng)中因妇,我們可以使用垃圾回收器问潭。runtime提供了幾個(gè)函數(shù)來確定一個(gè)對象的內(nèi)存區(qū)域是否可以被垃圾回收器掃描,以處理strong/weak引用婚被。這幾個(gè)函數(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 );

但通常情況下狡忙,我們不需要去主動(dòng)調(diào)用這些方法;在調(diào)用objc_registerClassPair時(shí)址芯,會(huì)生成合理的布局灾茁。在此不詳細(xì)介紹這些函數(shù)。

方法(methodLists)

方法操作主要有以下函數(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 );

// 獲取實(shí)例方法

Method class_getInstanceMethod ( Class cls, SEL name );

// 獲取類方法

Method class_getClassMethod ( Class cls, SEL name );

// 獲取所有方法的數(shù)組

Method * class_copyMethodList ( Class cls,unsignedint*outCount );

// 替代方法的實(shí)現(xiàn)

IMP class_replaceMethod ( Class cls, SEL name, IMP imp,constchar*types );

// 返回方法的具體實(shí)現(xiàn)

IMP class_getMethodImplementation ( Class cls, SEL name );

IMP class_getMethodImplementation_stret ( Class cls, SEL name );

// 類實(shí)例是否響應(yīng)指定的selector

BOOLclass_respondsToSelector ( Class cls, SEL sel );

class_addMethod的實(shí)現(xiàn)會(huì)覆蓋父類的方法實(shí)現(xiàn)谷炸,但不會(huì)取代本類中已存在的實(shí)現(xiàn)北专,如果本類中包含一個(gè)同名的實(shí)現(xiàn),則函數(shù)會(huì)返回NO旬陡。如果要修改已存在實(shí)現(xiàn)逗余,可以使用method_setImplementation。一個(gè)Objective-C方法是一個(gè)簡單的C函數(shù)季惩,它至少包含兩個(gè)參數(shù)–self和_cmd。所以腻格,我們的實(shí)現(xiàn)函數(shù)(IMP參數(shù)指向的函數(shù))至少需要兩個(gè)參數(shù)画拾,如下所示:

1

2

3

4

voidmyMethodIMP(idself, SEL _cmd)

{

// implementation ....

}

與成員變量不同的是,我們可以為類動(dòng)態(tài)添加方法菜职,不管這個(gè)類是否已存在青抛。

另外,參數(shù)types是一個(gè)描述傳遞給方法的參數(shù)類型的字符數(shù)組酬核,這就涉及到類型編碼蜜另,我們將在后面介紹。

class_getInstanceMethod嫡意、class_getClassMethod函數(shù)举瑰,與class_copyMethodList不同的是,這兩個(gè)函數(shù)都會(huì)去搜索父類的實(shí)現(xiàn)蔬螟。

class_copyMethodList函數(shù)此迅,返回包含所有實(shí)例方法的數(shù)組,如果需要獲取類方法,則可以使用class_copyMethodList(object_getClass(cls), &count)(一個(gè)類的實(shí)例方法是定義在元類里面)耸序。該列表不包含父類實(shí)現(xiàn)的方法忍些。outCount參數(shù)返回方法的個(gè)數(shù)。在獲取到列表后坎怪,我們需要使用free()方法來釋放它罢坝。

class_replaceMethod函數(shù),該函數(shù)的行為可以分為兩種:如果類中不存在name指定的方法搅窿,則類似于class_addMethod函數(shù)一樣會(huì)添加方法嘁酿;如果類中已存在name指定的方法,則類似于method_setImplementation一樣替代原方法的實(shí)現(xiàn)戈钢。

class_getMethodImplementation函數(shù)痹仙,該函數(shù)在向類實(shí)例發(fā)送消息時(shí)會(huì)被調(diào)用,并返回一個(gè)指向方法實(shí)現(xiàn)函數(shù)的指針殉了。這個(gè)函數(shù)會(huì)比method_getImplementation(class_getInstanceMethod(cls, name))更快开仰。返回的函數(shù)指針可能是一個(gè)指向runtime內(nèi)部的函數(shù),而不一定是方法的實(shí)際實(shí)現(xiàn)薪铜。例如众弓,如果類實(shí)例無法響應(yīng)selector,則返回的函數(shù)指針將是運(yùn)行時(shí)消息轉(zhuǎn)發(fā)機(jī)制的一部分隔箍。

class_respondsToSelector函數(shù)谓娃,我們通常使用NSObject類的respondsToSelector:或instancesRespondToSelector:方法來達(dá)到相同目的。

協(xié)議(objc_protocol_list)

協(xié)議相關(guān)的操作包含以下函數(shù):

1

2

3

4

5

6

7

8

// 添加協(xié)議

BOOLclass_addProtocol ( Class cls, Protocol *protocol );

// 返回類是否實(shí)現(xiàn)指定的協(xié)議

BOOLclass_conformsToProtocol ( Class cls, Protocol *protocol );

// 返回類實(shí)現(xiàn)的協(xié)議列表

Protocol * class_copyProtocolList ( Class cls,unsignedint*outCount );

class_conformsToProtocol函數(shù)可以使用NSObject類的conformsToProtocol:方法來替代蜒滩。

class_copyProtocolList函數(shù)返回的是一個(gè)數(shù)組滨达,在使用后我們需要使用free()手動(dòng)釋放。

版本(version)

版本相關(guān)的操作包含以下函數(shù):

1

2

3

4

5

// 獲取版本號(hào)

intclass_getVersion ( Class cls );

// 設(shè)置版本號(hào)

voidclass_setVersion ( Class cls,intversion );

其它

runtime還提供了兩個(gè)函數(shù)來供CoreFoundation的tool-free bridging使用俯艰,即:

1

2

Class objc_getFutureClass (constchar*name );

voidobjc_setFutureClass ( Class cls,constchar*name );

通常我們不直接使用這兩個(gè)函數(shù)捡遍。

實(shí)例(Example)

上面列舉了大量類操作的函數(shù),下面我們寫個(gè)實(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(@"==========================================================");

// 變量實(shí)例大小

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] ==========================================================

動(dòng)態(tài)創(chuàng)建類和對象

runtime的強(qiáng)大之處在于它能在運(yùn)行時(shí)創(chuàng)建類和對象画株。

動(dòng)態(tài)創(chuàng)建類

動(dòng)態(tài)創(chuàng)建類涉及到以下幾個(gè)函數(shù):

1

2

3

4

5

6

7

8

9

// 創(chuàng)建一個(gè)新類和元類

Class objc_allocateClassPair ( Class superclass,constchar*name, size_t extraBytes );

// 銷毀一個(gè)類及其相關(guān)聯(lián)的類

voidobjc_disposeClassPair ( Class cls );

// 在應(yīng)用中注冊由objc_allocateClassPair創(chuàng)建的類

voidobjc_registerClassPair ( Class cls );

objc_allocateClassPair函數(shù):如果我們要?jiǎng)?chuàng)建一個(gè)根類,則superclass指定為Nil啦辐。extraBytes通常指定為0谓传,該參數(shù)是分配給類和元類對象尾部的索引ivars的字節(jié)數(shù)。

為了創(chuàng)建一個(gè)新類芹关,我們需要調(diào)用objc_allocateClassPair续挟。然后使用諸如class_addMethod,class_addIvar等函數(shù)來為新創(chuàng)建的類添加方法侥衬、實(shí)例變量和屬性等庸推。完成這些后常侦,我們需要調(diào)用objc_registerClassPair函數(shù)來注冊類,之后這個(gè)新類就可以在程序中使用了贬媒。

實(shí)例方法和實(shí)例變量應(yīng)該添加到類自身上聋亡,而類方法應(yīng)該添加到類的元類上。

objc_disposeClassPair函數(shù)用于銷毀一個(gè)類际乘,不過需要注意的是坡倔,如果程序運(yùn)行中還存在類或其子類的實(shí)例,則不能調(diào)用針對類調(diào)用該方法脖含。

在前面介紹元類時(shí)罪塔,我們已經(jīng)有接觸到這幾個(gè)函數(shù)了,在此我們再舉個(gè)實(shí)例來看看這幾個(gè)函數(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

動(dòng)態(tài)創(chuàng)建對象

動(dòng)態(tài)創(chuàng)建對象的函數(shù)如下:

1

2

3

4

5

6

7

8

// 創(chuàng)建類實(shí)例

idclass_createInstance ( Class cls, size_t extraBytes );

// 在指定位置創(chuàng)建類實(shí)例

idobjc_constructInstance ( Class cls,void*bytes );

// 銷毀類實(shí)例

void* objc_destructInstance (idobj );

class_createInstance函數(shù):創(chuàng)建實(shí)例時(shí)征堪,會(huì)在默認(rèn)的內(nèi)存區(qū)域?yàn)轭惙峙鋬?nèi)存。extraBytes參數(shù)表示分配的額外字節(jié)數(shù)关拒。這些額外的字節(jié)可用于存儲(chǔ)在類定義中所定義的實(shí)例變量之外的實(shí)例變量佃蚜。該函數(shù)在ARC環(huán)境下無法使用。

調(diào)用class_createInstance的效果與+alloc方法類似着绊。不過在使用class_createInstance時(shí)谐算,我們需要確切的知道我們要用它來做什么。在下面的例子中归露,我們用NSString來測試一下該函數(shù)的實(shí)際效果:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末洲脂,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子剧包,更是在濱河造成了極大的恐慌恐锦,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疆液,死亡現(xiàn)場離奇詭異踩蔚,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)枚粘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來飘蚯,“玉大人馍迄,你說我怎么就攤上這事【种瑁” “怎么了攀圈?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長峦甩。 經(jīng)常有香客問我赘来,道長现喳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任犬辰,我火速辦了婚禮嗦篱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘幌缝。我一直安慰自己灸促,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布涵卵。 她就那樣靜靜地躺著浴栽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪轿偎。 梳的紋絲不亂的頭發(fā)上典鸡,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天,我揣著相機(jī)與錄音坏晦,去河邊找鬼萝玷。 笑死,一個(gè)胖子當(dāng)著我的面吹牛英遭,可吹牛的內(nèi)容都是我干的间护。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼挖诸,長吁一口氣:“原來是場噩夢啊……” “哼汁尺!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起多律,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤痴突,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后狼荞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辽装,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年相味,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了拾积。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,727評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡丰涉,死狀恐怖拓巧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情一死,我是刑警寧澤肛度,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站投慈,受9級(jí)特大地震影響承耿,放射性物質(zhì)發(fā)生泄漏冠骄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一加袋、第九天 我趴在偏房一處隱蔽的房頂上張望凛辣。 院中可真熱鬧,春花似錦锁荔、人聲如沸蟀给。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽跋理。三九已至,卻和暖如春恬总,著一層夾襖步出監(jiān)牢的瞬間前普,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工壹堰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拭卿,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓贱纠,卻偏偏與公主長得像峻厚,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子谆焊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容