Runtime概念
OC是基于C的,它為C添加了面向?qū)ο蟮奶匦裕鼘⒑芏囔o態(tài)語(yǔ)言在編譯和鏈接時(shí)期做的事情放到了runtime運(yùn)行時(shí)來(lái)處理,可以說(shuō)runtime是我們OC的幕后工作者
1.runtime簡(jiǎn)稱(chēng)運(yùn)行時(shí),是一套底層的c語(yǔ)言API,而OC就是運(yùn)行時(shí)機(jī)制,也就是運(yùn)行的一些機(jī)制,其中最主要的是消息機(jī)制。
objc在向一個(gè)對(duì)象發(fā)送消息時(shí),runtime庫(kù)會(huì)根據(jù)對(duì)象的isa指針找到該對(duì)象實(shí)際所屬的類(lèi),然后在該類(lèi)中的方法列表以及其父類(lèi)方法列表中尋找方法運(yùn)行,然后在發(fā)送消息的時(shí)候,objc_msfSend方法不會(huì)返回值,所謂的返回內(nèi)容都是具體調(diào)用時(shí)執(zhí)行的,那么如果想一個(gè)nil對(duì)象發(fā)送消息,首先在尋找對(duì)象的isa指針時(shí)就是0地址,返回了所以不會(huì)出現(xiàn)任何錯(cuò)誤。
對(duì)于c語(yǔ)言,函數(shù)的調(diào)用在編譯的時(shí)候會(huì)決定調(diào)用哪個(gè)函數(shù)
而OC的函數(shù)調(diào)用成為消息發(fā)送,屬于動(dòng)態(tài)調(diào)用過(guò)程,在編譯的時(shí)候并不能決定真正調(diào)用哪個(gè)函數(shù),只有在真正運(yùn)行的時(shí)候
才會(huì)根據(jù)函數(shù)的名稱(chēng)找到對(duì)應(yīng)的函數(shù)來(lái)調(diào)用。
2.實(shí)際上,在編譯階段,OC可以調(diào)用任何函數(shù),及時(shí)這個(gè)函數(shù)并未實(shí)現(xiàn),只有聲明過(guò)就不會(huì)報(bào)錯(cuò),只有當(dāng)運(yùn)行的時(shí)候才會(huì)報(bào)錯(cuò),這是因?yàn)镺C是運(yùn)行時(shí)動(dòng)態(tài)調(diào)用的,而C語(yǔ)言調(diào)用未實(shí)現(xiàn)的函數(shù)就會(huì)報(bào)錯(cuò)。
runtime 消息機(jī)制
我們寫(xiě)OC代碼,它在運(yùn)行的時(shí)候也是會(huì)轉(zhuǎn)換成runtime方式運(yùn)行。任何方法調(diào)用的本質(zhì):就是發(fā)送一個(gè)消息(用runtime發(fā)送消息,OC底層實(shí)現(xiàn)通過(guò)runtime實(shí)現(xiàn))。
消息機(jī)制原理:
比如我們創(chuàng)建一個(gè)對(duì)象[[NSObject alloc]init]
,最終被轉(zhuǎn)換為幾萬(wàn)行代碼,其實(shí)可以看到調(diào)用方法的本質(zhì)就是發(fā)送消息,[[NSObject alloc]init]
語(yǔ)句發(fā)了兩個(gè)消息,第一次發(fā)了alloc消息,第二次發(fā)了init消息。利用這個(gè)功能我們可以探究底層,比如block的實(shí)現(xiàn)原理
利用runtime可以做一些oc不容易的實(shí)現(xiàn)功能;
1、獲得某個(gè)類(lèi)的所有成員變量、成員方法。
//獲取實(shí)例變量
unsignedintcount =0;
Ivar*ivars =class_copyIvarList([RuntimeToolsclass], &count);
for(inti =0; i
Ivarivar = ivars[i];
//獲取實(shí)例變量的名稱(chēng)
constchar*ivarName =ivar_getName(ivar);
//獲取成員變量的類(lèi)型
constchar*ivarKey =ivar_getTypeEncoding(ivar);
NSLog(@"名稱(chēng):%s,類(lèi)型:%s",ivarName,ivarKey);
}
//獲取類(lèi)的方法
unsignedintmethodCount =0;
//獲取RuntimeTools所有的類(lèi)方法
Method*allMethods =class_copyMethodList([RuntimeToolsclass], &methodCount);
for(inti =0; i < methodCount; i++) {
Methodmethod = allMethods[i];
SELmethodSel =method_getName(method);
constchar*name =sel_getName(methodSel);
NSLog(@"%s",name);
}
2.為某個(gè)類(lèi)的category添加一個(gè)新屬性
在category中不能添加成員變量,OC類(lèi)是由class類(lèi)型來(lái)表示的,它實(shí)際上是一個(gè)指向objc_class結(jié)構(gòu)體的指針定義如下:
struct objc_class {
Class isaOBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; //父類(lèi)
cons tchar*name OBJC2_UNAVAILABLE; //類(lèi)名
long version OBJC2_UNAVAILABLE; 、//類(lèi)的版本信息
long info OBJC2_UNAVAILABLE;//類(lèi)信息
long instance_size OBJC2_UNAVAILABLE;//類(lèi)大小
struct objc_ivar_list *ivarsOBJC2_UNAVAILABLE; //該類(lèi)的成員變量鏈表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; //該類(lèi)的方法定義鏈表
struct objc_cache *cache OBJC2_UNAVAILABLE; //方法緩存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; //協(xié)議鏈表
#endif
} OBJC2_UNAVAILABLE;
在runtime中,objc_class結(jié)構(gòu)體大小是固定的,不可能往這個(gè)結(jié)構(gòu)體中添加數(shù)據(jù),只能修改。所以ivars指向的是一個(gè)固定區(qū)域,只能修改成員變量值,不能增加成員變量。method_list是一個(gè)二維數(shù)組,所以可以修改methodLists的值來(lái)增加成員方法。雖然沒(méi)辦法擴(kuò)展methodLists指向的內(nèi)存區(qū)域,卻可以改變這個(gè)內(nèi)存區(qū)域的值(存儲(chǔ)的指針)。因此可以動(dòng)態(tài)添加方法,不能添加成員變量
@interfaceRuntimeTools (Category)
@property(nonatomic,copy)NSString*lastName;
@end
生成set和get方法
- (void)setLastName:(NSString*)lastName
{
objc_setAssociatedObject(self,"lastName", lastName,OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString*)lastName{
return objc_getAssociatedObject(self,"lastName");
}
使用runtime中objc_getAssociatedObject()和objc_setAssociatedObject()方法,本質(zhì)上只是為對(duì)象添加了對(duì)lastName的屬性關(guān)聯(lián),達(dá)到新屬性的作用
3.為某個(gè)類(lèi)添加一個(gè)新方法
class_addMethod([RuntimeToolsclass],@selector(newMethod), (IMP)myAddfunction,0);
[pperformSelector:@selector(newMethod)];
4.動(dòng)態(tài)交換2個(gè)方法的功能
Methodm1 =class_getInstanceMethod([selfclass],@selector(go));
Methodm2 =class_getClassMethod([selfclass],@selector(run));
method_exchangeImplementations(m1, m2);
交換方法使用場(chǎng)景:項(xiàng)目中的某個(gè)功能,在項(xiàng)目中需要多次被引用,當(dāng)項(xiàng)目的需求發(fā)生改變是,要使用另一個(gè)功能代替這個(gè)功能,且要求不改變舊的項(xiàng)目,那么,我們可以在分類(lèi)中,再寫(xiě)一個(gè)新的方法,然后交換2個(gè)方法的實(shí)現(xiàn)。
5.改變某個(gè)對(duì)象的私有變量的值
unsignedintcount =0;
Ivar *allList = class_copyIvarList([personclass], &count);
Ivar ivv = allList[0];//從第一個(gè)例子getAllVariable中輸出的控制臺(tái)信息,我們可以看到name為第一個(gè)實(shí)例屬性。
object_setIvar(per, ivv,@"Mike");//name屬性Tom被強(qiáng)制改為Mike。
NSLog(@"改變之后的person:%@",per)
6.利用runtime獲取所有屬性來(lái)重寫(xiě)歸檔解檔方法
// 解檔方法
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self) {
unsigned int count;
Ivar *ivars = class_copyIvarList([self class], &count);
for (int i = 0; i <count; i++) {
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
//變量名轉(zhuǎn)換成NSSting類(lèi)型
NSString *nameStr = [NSString stringWithFormat:@"%s",name];
//解檔
id value = [aDecoder decodeObjectForKey:nameStr];
[self setValue:value forKey:nameStr];
}
free(ivars);
}
return self;
}
// 歸檔調(diào)用方法
- (void)encodeWithCoder:(NSCoder *)aCoder {
// 獲取所有成員變量
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i <outCount; i++) {
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
//變量名換成字符串
NSString *key = [NSString stringWithFormat:@"%s",name];
id value = [self valueForKey:key];
[aCoder setValue:value forKey:key];
}
}
如何應(yīng)用運(yùn)行時(shí)
1、可以將某些OC代碼轉(zhuǎn)換成運(yùn)行時(shí)的代碼,探究底層,比如block的實(shí)現(xiàn)原理
2、攔截系統(tǒng)自帶的方法調(diào)用。比如imageWithName:,alloc;viewDidLoad
3、實(shí)現(xiàn)分類(lèi)也可以增加屬性
4、實(shí)現(xiàn)NSCoding自動(dòng)歸檔和自動(dòng)解檔
5、實(shí)現(xiàn)字典和模型的自動(dòng)轉(zhuǎn)換。
動(dòng)態(tài)交換2個(gè)方法
、、、