1.首先讓我們來(lái)了解Class的定義:
Objective-C類是由Class類型來(lái)表示的,它實(shí)際上是一個(gè)指向objc_class結(jié)構(gòu)體的指針。它的定義如下:typedef struct objc_class *Class;
2.查看objc/runtime.h中objc_class結(jié)構(gòu)體的定義如下:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父類
const char *name OBJC2_UNAVAILABLE; // 類名
long version OBJC2_UNAVAILABLE; // 類的版本信息,默認(rèn)為0
long info OBJC2_UNAVAILABLE; // 類信息,供運(yùn)行期使用的一些位標(biāo)識(shí)
long instance_size OBJC2_UNAVAILABLE; // 該類的實(shí)例變量大小
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中,所有的類自身也是一個(gè)對(duì)象,這個(gè)對(duì)象的Class里面也有一個(gè)isa指針,它指向metaClass(元類),我們會(huì)在后面介紹它。
super_class:指向該類的父類,如果該類已經(jīng)是最頂層的根類(如NSObject或NSProxy),則super_class為NULL。
cache:用于緩存最近使用的方法。一個(gè)接收者對(duì)象接收到一個(gè)消息時(shí),它會(huì)根據(jù)isa指針去查找能夠響應(yīng)這個(gè)消息的對(duì)象。在實(shí)際使用中,這個(gè)對(duì)象只有一部分方法是常用的,很多方法其實(shí)很少用或者根本用不上。這種情況下,如果每次消息來(lái)時(shí),我們都是methodLists中遍歷一遍,性能勢(shì)必很差。這時(shí),cache就派上用場(chǎng)了。在我們每次調(diào)用過(guò)一個(gè)方法后,這個(gè)方法就會(huì)被緩存到cache列表中,下次調(diào)用的時(shí)候runtime就會(huì)優(yōu)先去cache中查找,如果cache沒(méi)有,才去methodLists中查找方法。這樣,對(duì)于那些經(jīng)常用到的方法的調(diào)用,但提高了調(diào)用的效率。
version:我們可以使用這個(gè)字段來(lái)提供類的版本信息。這對(duì)于對(duì)象的序列化非常有用,它可是讓我們識(shí)別出不同類定義版本中實(shí)例變量布局的改變
SEL
SEL又叫選擇器,是表示一個(gè)方法的selector的指針,其定義如下:
1.typedef struct objc_selector *SEL;
objc_selector結(jié)構(gòu)體的詳細(xì)定義沒(méi)有在頭文件中找到。方法的selector用于表示運(yùn)行時(shí)方 法的名字。Objective-C在編譯時(shí),會(huì)依據(jù)每一個(gè)方法的名字、參數(shù)序列,生成一個(gè)唯一的整型標(biāo)識(shí)(Int類型的地址),這個(gè)標(biāo)識(shí)就是SEL。如下 代碼所示:
1.SEL sel1 = @selector(method1);
2.NSLog(@"sel : %p", sel1);
上面的輸出為:
2014-10-30 18:40:07.518 RuntimeTest[52734:466626] sel : 0x100002d72
兩個(gè)類之間,不管它們是父類與子類的關(guān)系,還是之間沒(méi)有這種關(guān)系,只要方法名相同,那么方法的SEL就是一樣的。每一個(gè)方法都對(duì)應(yīng)著一個(gè)SEL。所以在 Objective-C同一個(gè)類(及類的繼承體系)中,不能存在2個(gè)同名的方法,即使參數(shù)類型不同也不行。相同的方法只能對(duì)應(yīng)一個(gè)SEL。這也就導(dǎo)致 Objective-C在處理相同方法名且參數(shù)個(gè)數(shù)相同但類型不同的方法方面的能力很差。如在某個(gè)類中定義以下兩個(gè)方法:
1.- (void)setWidth:(int)width;
2.- (void)setWidth:(double)width;
當(dāng)然,不同的類可以擁有相同的selector,這個(gè)沒(méi)有問(wèn)題。不同類的實(shí)例對(duì)象執(zhí)行相同的selector時(shí),會(huì)在各自的方法列表中去根據(jù)selector去尋找自己對(duì)應(yīng)的IMP。
工程中的所有的SEL組成一個(gè)Set集合,Set的特點(diǎn)就是唯一,因此SEL是唯一的。因此,如果我們想到這個(gè)方法集合中查找某個(gè)方法時(shí),只需要去 找到這個(gè)方法對(duì)應(yīng)的SEL就行了,SEL實(shí)際上就是根據(jù)方法名hash化了的一個(gè)字符串,而對(duì)于字符串的比較僅僅需要比較他們的地址就可以了,可以說(shuō)速度 上無(wú)語(yǔ)倫比??!但是,有一個(gè)問(wèn)題,就是數(shù)量增多會(huì)增大hash沖突而導(dǎo)致的性能下降(或是沒(méi)有沖突,因?yàn)橐部赡苡玫氖莗erfect hash)。但是不管使用什么樣的方法加速,如果能夠?qū)⒖偭繙p少(多個(gè)方法可能對(duì)應(yīng)同一個(gè)SEL),那將是最犀利的方法。那么,我們就不難理解,為什么 SEL僅僅是函數(shù)名了。
本質(zhì)上,SEL只是一個(gè)指向方法的指針(準(zhǔn)確的說(shuō),只是一個(gè)根據(jù)方法名hash化了的KEY值,能唯一代表一個(gè)方法),它的存在只是為了加快方法的查詢速度。這個(gè)查找過(guò)程我們將在下面討論。
我們可以在運(yùn)行時(shí)添加新的selector,也可以在運(yùn)行時(shí)獲取已存在的selector,我們可以通過(guò)下面三種方法來(lái)獲取SEL:
1. sel_registerName函數(shù)
2. Objective-C編譯器提供的@selector()
3. NSSelectorFromString()方法
IMP
IMP實(shí)際上是一個(gè)函數(shù)指針,指向方法實(shí)現(xiàn)的首地址。其定義如下:
1.id (*IMP)(id, SEL, ...)
這個(gè)函數(shù)使用當(dāng)前CPU架構(gòu)實(shí)現(xiàn)的標(biāo)準(zhǔn)的C調(diào)用約定。第一個(gè)參數(shù)是指向self的指針(如果是實(shí)例方法,則是類實(shí)例的內(nèi)存地址;如果是類方法,則是指向元類的指針),第二個(gè)參數(shù)是方法選擇器(selector),接下來(lái)是方法的實(shí)際參數(shù)列表。
前面介紹過(guò)的SEL就是為了查找方法的最終實(shí)現(xiàn)IMP的。由于每個(gè)方法對(duì)應(yīng)唯一的SEL,因此我們可以通過(guò)SEL方便快速準(zhǔn)確地獲得它所對(duì)應(yīng)的 IMP,查找過(guò)程將在下面討論。取得IMP后,我們就獲得了執(zhí)行這個(gè)方法代碼的入口點(diǎn),此時(shí),我們就可以像調(diào)用普通的C語(yǔ)言函數(shù)一樣來(lái)使用這個(gè)函數(shù)指針 了。
通過(guò)取得IMP,我們可以跳過(guò)Runtime的消息傳遞機(jī)制,直接執(zhí)行IMP指向的函數(shù)實(shí)現(xiàn),這樣省去了Runtime消息傳遞過(guò)程中所做的一系列查找操作,會(huì)比直接向?qū)ο蟀l(fā)送消息高效一些。
Method
Method用于表示類定義中的方法,則定義如下:
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE; // 方法名
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE; // 方法實(shí)現(xiàn)
}
我們可以看到該結(jié)構(gòu)體中包含一個(gè)SEL和IMP,實(shí)際上相當(dāng)于在SEL和IMP之間作了一個(gè)映射。有了SEL,我們便可以找到對(duì)應(yīng)的IMP,從而調(diào)用方法的實(shí)現(xiàn)代碼。
runtime type說(shuō)明
1.type定義參考:https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
2."v@:@",解釋v-返回值void類型,@-self指針id類型,:-SEL指針SEL類型,@-函數(shù)第一個(gè)參數(shù)為id類型
3."@@:",解釋@-返回值id類型,@-self指針id類型,:-SEL指針SEL類型,
runtime使用場(chǎng)景:
1.動(dòng)態(tài)創(chuàng)建類,添加屬性、成員變量、方法和協(xié)議。
2.動(dòng)態(tài)交互兩個(gè)方法的實(shí)現(xiàn)(Method Swizzling)
3.分類添加成員變量 3.1、類別能不能添加Ivar。
提示:類別不能添加成員變量(Ivar),因?yàn)樵赾ategory對(duì)于的結(jié)構(gòu)體(category_t)中沒(méi)有保存成員變量的結(jié)構(gòu)體指針。
無(wú)法添加實(shí)例變量的(因?yàn)樵谶\(yùn)行期,對(duì)象的內(nèi)存布局已經(jīng)確定,如果添加實(shí)例變量就會(huì)破壞類的內(nèi)部局部,這對(duì)編譯語(yǔ)言來(lái)說(shuō)是災(zāi)難性的。
4.自動(dòng)實(shí)現(xiàn)歸檔和解歸檔
5.模型字典相互轉(zhuǎn)化
Demo如下:
動(dòng)態(tài)創(chuàng)建類
//動(dòng)態(tài)創(chuàng)建類
/*
參數(shù)1:父類
參數(shù)2:類的名字
參數(shù)3:額外分配的空間,一般都是0
*/
Class cls = objc_allocateClassPair([NSObject class], "Person", 0);
//在注冊(cè)之前一定要添加需要的方法,成員變量等。
/*
參數(shù)1:該成員變量添加到哪個(gè)類中。
參數(shù)2:成員變量的名字
參數(shù)3:成員變量的大小
參數(shù)4:log2(成員變量的大小)
參數(shù)5:變量的類型:參考資源素材中的“Runtime Type Encodings”
*/
class_addIvar(cls, "_name", sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString *));
class_addIvar(cls, "_age", sizeof(int), log2(sizeof(int)), @encode(int));
添加方法
/*
//添加方法
class_addMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>, <#IMP imp#>, <#const char *types#>)
//添加屬性
class_addProperty(<#__unsafe_unretained Class cls#>, <#const char *name#>, <#const objc_property_attribute_t *attributes#>, <#unsigned int attributeCount#>)
//添加協(xié)議
class_addProtocol(<#__unsafe_unretained Class cls#>, <#Protocol *protocol#>)
*/
//必須注冊(cè)后才能用,注冊(cè)后類的結(jié)構(gòu)就確定好了。
objc_registerClassPair(cls);
char *type = @encode(void);
printf("%s",type);
id person = [[cls alloc] init];
//賦值
[person setValue:@"小明" forKey:@"_name"];
[person setValue:@(20) forKey:@"_age"];
NSString *name = [person valueForKey:@"_name"];
int age = [[person valueForKey:@"_age"] intValue];
NSLog(@"name = %@ , age = %d",name, age);
//objc_setAssociatedObject(<#id object#>, <#const void *key#>, <#id value#>, objc_AssociationPolicy policy)
動(dòng)態(tài)交換兩個(gè)方法實(shí)現(xiàn): Method Swizzling
/*
動(dòng)態(tài)交換兩個(gè)方法實(shí)現(xiàn): Method Swizzling
*/
//獲取兩個(gè)方法的實(shí)現(xiàn)
Method test1 = class_getInstanceMethod([self class], @selector(test1));
Method test2 = class_getInstanceMethod([self class], @selector(test2));
//交互兩個(gè)方法的實(shí)現(xiàn)
method_exchangeImplementations(test1, test2);
Method request1 = class_getClassMethod([HttpRequest class], @selector(GET:));
Method request2 = class_getInstanceMethod([self class], @selector(customGet));
//交互兩個(gè)方法的實(shí)現(xiàn)
method_exchangeImplementations(request1, request2);
[HttpRequest GET:@"url"];
}
- (void)customGet
{
}
- (void)test1
{
NSLog(@"test111111方法調(diào)用了");
[self test1];
}
- (void)test2
{
NSLog(@"test22222方法調(diào)用了");
}