RunTime顧名思義運(yùn)行時(shí)穷劈,就是系統(tǒng)在運(yùn)行的時(shí)候的一些機(jī)制薇正,最主要的是消息機(jī)制片酝。對(duì)于C語(yǔ)言,函數(shù)的調(diào)用會(huì)在編譯時(shí)決定調(diào)用哪個(gè)函數(shù)挖腰,編譯完成后,會(huì)按順序執(zhí)行练湿,無(wú)二義性猴仑。OC的調(diào)用成為消息轉(zhuǎn)發(fā),編譯時(shí)不能決定調(diào)哪個(gè)函數(shù)肥哎,只有在真正運(yùn)行的時(shí)候通過(guò)函數(shù)名找到對(duì)應(yīng)的函數(shù)調(diào)用辽俗,屬于動(dòng)態(tài)調(diào)用的過(guò)程。
- 什么是runtime篡诽?
我們寫(xiě)的oc代碼在運(yùn)行的時(shí)候崖飘,會(huì)被轉(zhuǎn)化為runtime的C代碼執(zhí)行。
e.g.:[obj doSomething];方法被轉(zhuǎn)化為objc_msgSend(obj, @selector(doSomething));
OC中的obj都繼承自NSObject杈女,類(lèi)的實(shí)例在Runtime中的結(jié)構(gòu)如下:
/// 描述類(lèi)中的一個(gè)方法
typedef struct objc_method *Method;
/// 實(shí)例變量
typedef struct objc_ivar *Ivar;
/// 類(lèi)別
typedef struct objc_category *Category;
/// 描述類(lèi)中的屬性
typedef struct objc_property *objc_property_t;
類(lèi)在runtime中的表示:
//類(lèi)在runtime中的表示
struct objc_class {
Class isa; //指針
//實(shí)例的isa指向類(lèi)對(duì)象朱浴,類(lèi)對(duì)象的isa指向元類(lèi)
#if !__OBJC2__
Class super_class; //指向父類(lèi)
const char *name; //類(lèi)名
long version; //當(dāng)前版本
long info;
long instance_size;
struct objc_ivar_list *ivars; //成員變量列表
struct objc_method_list **methodLists; //方法列表
struct objc_cache *cache; //緩存
struct objc_protocol_list *protocols; //協(xié)議列表
#endif
} OBJC2_UNAVAILABLE;
- runtime的使用:
1、獲取類(lèi)中的屬性方法列表等
我們可以通過(guò)runtime的一些方法獲取屬性列表达椰、成員變量列表翰蠢、方法列表、遵循的協(xié)議列表
unsigned int count;
//獲取屬性列表
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i=0; i<count; i++) {
const char *propertyName = property_getName(propertyList[i]);
NSLog(@"property---->%@", [NSString stringWithUTF8String:propertyName]);
}
//獲取方法列表
Method *methodList = class_copyMethodList([self class], &count);
for (unsigned int i; i<count; i++) {
Method method = methodList[i];
NSLog(@"method---->%@", NSStringFromSelector(method_getName(method)));
}
//獲取成員變量列表
Ivar *ivarList = class_copyIvarList([self class], &count);
for (unsigned int i; i<count; i++) {
Ivar myIvar = ivarList[i];
const char *ivarName = ivar_getName(myIvar);
NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]);
}
//獲取協(xié)議列表
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
for (unsigned int i; i<count; i++) {
Protocol *myProtocal = protocolList[i];
const char *protocolName = protocol_getName(myProtocal);
NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]);
}
2啰劲、方法調(diào)用過(guò)程(在運(yùn)行時(shí))
- 如果是實(shí)例對(duì)象調(diào)用實(shí)例方法梁沧,會(huì)在實(shí)例的isa指針指向的對(duì)象(類(lèi)對(duì)象)操作。
- 如果是類(lèi)對(duì)象調(diào)用類(lèi)方法蝇裤,會(huì)在類(lèi)對(duì)象的isa指針指向的對(duì)象(元類(lèi)/基類(lèi))操作廷支。
具體操作:
- 首先,在相應(yīng)操作對(duì)象的緩存方法列表中查找要調(diào)用的方法栓辜,如果找到蚜迅,轉(zhuǎn)向相應(yīng)實(shí)現(xiàn)蛤肌,并執(zhí)行。
- 如果未找到,在相應(yīng)操作對(duì)象的方法列表中查找质和,如果找到,轉(zhuǎn)向相應(yīng)實(shí)現(xiàn)喳魏,并執(zhí)行卖毁。
- 如果未找到,在操作對(duì)象的父類(lèi)指針?biāo)赶虻膶?duì)象中操作1贩毕,2步悯许。
- 以此類(lèi)推,如果到根類(lèi)還未找到辉阶,轉(zhuǎn)向攔截調(diào)用方法先壕。
- 如果未實(shí)現(xiàn)攔截調(diào)用瘩扼,程序報(bào)錯(cuò)。
3垃僚、攔截調(diào)用
上面說(shuō)到了集绰,如果未找到方法,則轉(zhuǎn)向攔截調(diào)用谆棺。
那什么是攔截調(diào)用呢栽燕?
攔截調(diào)用就是,在找不到調(diào)用的方法程序崩潰之前改淑,你有機(jī)會(huì)重寫(xiě)NSObject類(lèi)中的四個(gè)方法來(lái)做處理:
+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resolveInstanceMethod:(SEL)sel;
//后兩個(gè)方法需要轉(zhuǎn)發(fā)到其他的類(lèi)處理
- (id)forwardingTargetForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
- 第一個(gè)方法是當(dāng)你調(diào)用一個(gè)不存在的類(lèi)方法的時(shí)候碍岔,會(huì)調(diào)用這個(gè)方法,默認(rèn)返回NO朵夏,你可以加上自己的處理然后返回YES蔼啦。
- 第二個(gè)方法和第一個(gè)方法相似,只不過(guò)處理的是實(shí)例方法仰猖。
- 第三個(gè)方法是將你調(diào)用的不存在的方法重定向到一個(gè)其他聲明了這個(gè)方法的類(lèi)捏肢,只需要你返回一個(gè)有這個(gè)方法的target。
- 第四個(gè)方法是將你調(diào)用的不存在的方法打包成NSInvocation傳給你亮元。做完你自己的處理后猛计,調(diào)用invokeWithTarget:方法讓某個(gè)target觸發(fā)這個(gè)方法。
4爆捞、動(dòng)態(tài)添加方法
重寫(xiě)了攔截方法并返回YES奉瘤,我們要采取什么措施補(bǔ)救呢?
可以通過(guò)傳遞的SEL類(lèi)型的selector動(dòng)態(tài)添加方法煮甥。
舉個(gè)栗子:
obj 對(duì)象隱式調(diào)用一個(gè)不存在的方法doSomething
[obj performSelector:@selector(doSomething) withObject:nil];
然后在obj內(nèi)部的攔截方法動(dòng)態(tài)添加方法
void runAddMethod(id obj, SEL _cmd, NSString *str) {
NSLog(@"add C IMP");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if ([NSStringFromSelector(sel) isEqualToString:@"doSomething"]) {
class_addMethod(self, sel, (IMP)runAddMethod, "v@:*");
}
return YES;
}
class_addMethod的四個(gè)參數(shù)解釋
- Class cls 給哪個(gè)類(lèi)添加方法盗温,本例中是self。
- SEL name 添加的方法成肘,本例中是重寫(xiě)的攔截調(diào)用傳進(jìn)來(lái)的selector卖局。
- IMP imp 方法的實(shí)現(xiàn),C方法的方法實(shí)現(xiàn)可以直接獲得双霍。如果是OC方法砚偶,可以用+ (IMP)instanceMethodForSelector:(SEL)aSelector;獲得方法的實(shí)現(xiàn)。
- “v@:*”方法的簽名洒闸,代表有一個(gè)參數(shù)的方法染坯,下面還會(huì)講到。
5.關(guān)聯(lián)對(duì)象
如果你想用系統(tǒng)類(lèi)丘逸,在不滿足需求情況下单鹿,你需要額外添加屬性,多數(shù)會(huì)使用繼承深纲,然后定義屬性仲锄【⒚睿總是繼承不是太麻煩,有了runtime儒喊,你不用費(fèi)那大勁了镣奋。接下來(lái),就來(lái)看看runtime是如何動(dòng)態(tài)添加屬性怀愧,獲取屬性的唆途。
//首先定義一個(gè)全局變量,用它的地址作為關(guān)聯(lián)對(duì)象的key
static char associatedObjectKey;
//設(shè)置關(guān)聯(lián)對(duì)象
objc_setAssociatedObject(target, &associatedObjectKey, @"添加的字符串屬性", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//獲取關(guān)聯(lián)對(duì)象
NSString *string = objc_getAssociatedObject(target, &associatedObjectKey);
NSLog(@"AssociatedObject = %@", string);
objc_setAssociatedObject的四個(gè)參數(shù):
- id object給誰(shuí)設(shè)置關(guān)聯(lián)對(duì)象掸驱。
- const void *key關(guān)聯(lián)對(duì)象唯一的key,獲取時(shí)會(huì)用到没佑。
- id value關(guān)聯(lián)對(duì)象毕贼。
- objc_AssociationPolicy關(guān)聯(lián)策略,有以下幾種策略:
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_RETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403
}```
objc_getAssociatedObject的兩個(gè)參數(shù):
- id object獲取誰(shuí)的關(guān)聯(lián)對(duì)象蛤奢。
- const void *key根據(jù)這個(gè)唯一的key獲取關(guān)聯(lián)對(duì)象鬼癣。
可以將上述兩個(gè)方法寫(xiě)在當(dāng)前類(lèi)的類(lèi)別的方法中,方便調(diào)用啤贩。
//添加關(guān)聯(lián)對(duì)象
- (void)addAssociatedObject:(id)object{
objc_setAssociatedObject(self, @selector(getAssociatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
//獲取關(guān)聯(lián)對(duì)象 - (id)getAssociatedObject{
return objc_getAssociatedObject(self, _cmd);//這里面我們把getAssociatedObject方法的地址作為唯一的key待秃,_cmd代表當(dāng)前調(diào)用方法的地址。
}```
看到這里估計(jì)大家有些疲累了痹屹,別急章郁,馬上就結(jié)束了!
6.交換方法
就是將兩個(gè)方法的實(shí)現(xiàn)交換志衍。例如暖庄,將A方法和B方法交換,調(diào)用A方法的時(shí)候楼肪,就會(huì)執(zhí)行B方法中的代碼培廓,反之亦然。
我們來(lái)試一下 定義一個(gè)UIViewController的category
/**
load方法會(huì)在類(lèi)第一次加載的時(shí)候被調(diào)用
調(diào)用的時(shí)間比較靠前春叫,適合在這個(gè)方法里做方法交換
*/
+(void)load{
//方法交換應(yīng)該被保證肩钠,在程序中只會(huì)執(zhí)行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//獲得viewController的生命周期方法的selector
SEL systemSel = @selector(viewWillAppear:);
//自己實(shí)現(xiàn)的將要被交換的方法的selector
SEL customeSel = @selector(custome_viewWillAppear:);
//兩個(gè)方法的Method
Method systemMethod = class_getInstanceMethod([self class], systemSel);
Method customeMethod = class_getInstanceMethod([self class], customeSel);
//首先動(dòng)態(tài)添加方法,實(shí)現(xiàn)是被交換的方法暂殖,返回值表示添加成功還是失敗
BOOL isAdd = class_addMethod(self, systemSel, method_getImplementation(customeMethod), method_getTypeEncoding(customeMethod));
if (isAdd) {
//如果成功价匠,說(shuō)明類(lèi)中不存在這個(gè)方法的實(shí)現(xiàn)
//將被交換方法的實(shí)現(xiàn)替換到這個(gè)并不存在的實(shí)現(xiàn)
class_replaceMethod(self, customeSel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
}else{
//否則,交換兩個(gè)方法的實(shí)現(xiàn)
method_exchangeImplementations(systemMethod, customeMethod);
}
});
}
- (void)custome_viewWillAppear:(BOOL)animated{
//這時(shí)候調(diào)用自己央星,看起來(lái)像是死循環(huán)
//但是其實(shí)自己的實(shí)現(xiàn)已經(jīng)被替換了
[self custome_viewWillAppear:animated];//這里 是去執(zhí)行系統(tǒng)的viewWillApper:方法
NSLog(@"custome");
}```
寫(xiě)個(gè)通用的方法供外部調(diào)
- (void)changeMethodWithTarget:(id)obj fromMethod:(SEL)fromMethod toMethod:(SEL)toMethod
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method m1 = class_getInstanceMethod([obj class], fromMethod);
Method m2 = class_getInstanceMethod([obj class], toMethod);
BOOL isAdd = class_addMethod([obj class], fromMethod, method_getImplementation(m2), method_getTypeEncoding(m2));
if (isAdd) {
class_replaceMethod([obj class], fromMethod, method_getImplementation(m2), method_getTypeEncoding(m2));
}else{
method_exchangeImplementations(m1, m2);
}
});
}```
以上是runtime的基本原理何使用講解霞怀,有什么不對(duì)或不足的地方,希望大家多多指教莉给!
下一章:RunLoop與多線程的原理和使用