一垮抗、RunTime是什么?
定義:RunTime實(shí)際上是一個(gè)庫(kù),這個(gè)庫(kù)使我們可以在程序運(yùn)行時(shí)動(dòng)態(tài)的創(chuàng)建對(duì)象、檢查對(duì)象梆掸,修改類(lèi)和對(duì)象的方法宰睡。他的作用其實(shí)就是在程序運(yùn)行時(shí)做一些事情租冠。
下面我們來(lái)看看它的常用方法垄惧,前提引入頭文件 #,再去
這個(gè)字段設(shè)置為 NO(默認(rèn)為YES)炒辉,防止編譯器校驗(yàn)語(yǔ)法豪墅。
1:方法交換
Person *person = [[Person alloc] init];
Method m1 = class_getInstanceMethod(person.class, @selector(eat));
Method m2 = class_getInstanceMethod(person.class, @selector(run));
method_exchangeImplementations(m1, m2);
2:動(dòng)態(tài)添加方法
注意使用performSelector: 來(lái)調(diào)用,因?yàn)閷?duì)象在編譯階段是沒(méi)有addMethodTest方法的黔寇。防止編譯不過(guò)
//方法一:
Person *p = [[Person alloc]init];
IMP imp = class_getMethodImplementation(self.class, @selector(test));
Boolean success = class_addMethod(p.class, @selector(addMethodTest), imp, "v@");
[p performSelector:@selector(addMethodTest)];
//方法二:
Person *p = [[Person alloc]init];
void(^testImp)(void) = ^{
NSLog(@"***q*");
};
IMP imp = imp_implementationWithBlock(testImp);
Boolean success = class_addMethod(p.class, @selector(addMethodTest), imp, "v@");
[p performSelector:@selector(addMethodTest)];
3:動(dòng)態(tài)添加屬性
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, "key_name", name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
return (NSString *)objc_getAssociatedObject(self, "key_name");
}
4:遍歷屬性
注意:通過(guò)objc_setAssociatedObject 添加的屬性偶器,是不能在這里發(fā)現(xiàn)的
Person *person = [[Person alloc]init];
unsigned count =0;
Ivar * ivars = class_copyIvarList(person.class, &count);
for (int i =0; i < count; i ++) {
const char *s = ivar_getName(ivars[i]);
NSString * property = [NSString stringWithCString:s encoding:NSUTF8StringEncoding];
NSLog(@" property = %@", property);
}
二、它有什么作用
1缝裤、程序crash檢測(cè)
當(dāng)對(duì)象調(diào)用沒(méi)有實(shí)現(xiàn)的方法屏轰,即在程序運(yùn)行時(shí),Runtime 會(huì)監(jiān)測(cè)程序發(fā)生的意外憋飞,如果有crash發(fā)生霎苗,出于對(duì)用戶的體驗(yàn)考慮,runtime允許在即將crash前榛做,讓開(kāi)發(fā)者做一些事情去挽救APP唁盏,避免crash發(fā)生内狸。其中有三個(gè)階段可以處理crash。
實(shí)際開(kāi)發(fā)中沒(méi)有人傻到會(huì)去調(diào)用沒(méi)有實(shí)現(xiàn)的方法厘擂,如果有當(dāng)我沒(méi)說(shuō)[滑稽]昆淡!,但是當(dāng)項(xiàng)目大了之后刽严,解析數(shù)據(jù)昂灵,版本字段變更,升級(jí)后緩存未清理港庄,都有可能發(fā)生 unrecognized selector sent to…錯(cuò)誤倔既。
這時(shí)可以就可以考慮以下解決方案了!
方案A:Method resolution-方法解析鹏氧,解決
注意:添加方法時(shí):class_addMethod中,實(shí)例方法是[self class]佩谣,類(lèi)方法是objc_getMetaClass(“ViewController”), 因?yàn)轭?lèi)方法最終查找方法實(shí)現(xiàn)是去對(duì)象的元類(lèi)對(duì)象中查找把还,所以添加也是去元類(lèi)對(duì)象中添加
- (void)viewDidLoad {
[super viewDidLoad];
//類(lèi)方法
[ViewController performSelector:@selector(noMethod)];
//實(shí)例方法
[self performSelector:@selector(noMethod)];
}
- (void)test {
NSLog(@"攔截到 %@ crash",NSStringFromSelector(_cmd));
}
//實(shí)例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if ([NSStringFromSelector(sel) isEqualToString:@"noMethod"]) {
IMP imp = class_getMethodImplementation([self class], @selector(test));
class_addMethod([self class], sel, imp, "v@");
}
return [super resolveInstanceMethod:sel];
}
//類(lèi)方法
+ (BOOL)resolveClassMethod:(SEL)sel {
if ([NSStringFromSelector(sel) isEqualToString:@"noMethod"]) {
IMP imp = class_getMethodImplementation([ViewController class], @selector(test));
class_addMethod(objc_getMetaClass("ViewController"), sel, imp, "v@");
}
return [super resolveClassMethod:sel];
}
方案B:Fast forwarding-消息快速轉(zhuǎn)發(fā)
這一步比較簡(jiǎn)單,就是找一個(gè)能實(shí)現(xiàn)該方法的對(duì)象茸俭,讓這個(gè)對(duì)象去實(shí)現(xiàn)該方法吊履;
- (id)forwardingTargetForSelector:(SEL)aSelector {
if ([[Person new] respondsToSelector:aSelector]) {
return [Person new];
}
return [super forwardingTargetForSelector:aSelector];
}
方案C:Forwarding-消息慢轉(zhuǎn)發(fā)
當(dāng)然這里的慢是相對(duì)于方案B的,不是說(shuō)真的就是很慢调鬓。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if ([super methodSignatureForSelector:aSelector] == nil) {
NSMethodSignature *methodSign = [NSMethodSignature signatureWithObjCTypes:"v@:"];
return methodSign;
} else {
return [super methodSignatureForSelector:aSelector];
}
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = anInvocation.selector;
if ([[Person new] respondsToSelector:sel]) {
[anInvocation invokeWithTarget:[Person new]];
} else {
[super forwardInvocation:anInvocation];
}
}
注意:以上三個(gè)步驟艇炎,效率上 A > B >C,由于對(duì)象會(huì)緩存方法腾窝,如果方案A實(shí)現(xiàn)了缀踪,addMethod,那么下次調(diào)用該方法時(shí)虹脯,會(huì)直接從對(duì)象的method cache 里面直接調(diào)用驴娃,效率更高。A循集,B唇敞,C都實(shí)現(xiàn)了,只會(huì)執(zhí)行A咒彤!
2:實(shí)現(xiàn)NSDictionary 轉(zhuǎn) Model
就是利用runtime 遍歷model實(shí)例對(duì)象的屬性疆柔,然后通過(guò)KVC給這個(gè)屬性賦值。
3:Category中添加屬性
這里需要注意:添加的屬性不是真正這個(gè)屬性镶柱。而是關(guān)聯(lián)對(duì)象旷档。
真正添加屬性如下:
Class cat_cls = objc_allocateClassPair([NSObject class], "OBCat", 0);
class_addIvar(cat_cls, "_name", sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString*));
objc_registerClassPair(cat_cls);
id ob_cat = [[cat_cls alloc] init];
[ob_cat setValue:@"tom" forKey:@"name"];
添加屬性需要在實(shí)例化之后,注冊(cè)之前添加奸例,即在方法和
之間添加彬犯。如果在$\color{red}{objc_registerClassPair}之后添加無(wú)效向楼。還可能崩潰。這里涉及到OC中類(lèi)的成員變量的偏移量, 如果在類(lèi)注冊(cè)之后再addIvar的話會(huì)破壞原來(lái)類(lèi)成員變量正確的偏移量, 這樣的話會(huì)導(dǎo)致你訪問(wèn)的那個(gè)成員變量并不是你想訪問(wèn)的成員變量
4:通用埋點(diǎn)
利用runtime的方法交換谐区,監(jiān)聽(tīng)ViewController的停留時(shí)長(zhǎng)湖蜕,還能做到無(wú)侵入式。Hook生命周期的方法宋列,然后交換方法實(shí)現(xiàn)昭抒,然后在自定義的方法中,調(diào)用外部方法炼杖,并 invoke 原來(lái)生命周期的方法灭返。
詳情請(qǐng)點(diǎn)擊埋點(diǎn)專題
5:KVO
以上說(shuō)的是,而KVO則是
的一種運(yùn)用;
kvo底層也是通過(guò)runtime來(lái)實(shí)現(xiàn)的坤邪,當(dāng)某個(gè)對(duì)象設(shè)置kvo時(shí)熙含,runtime會(huì)在運(yùn)行時(shí),動(dòng)態(tài)的創(chuàng)建該類(lèi)的子類(lèi)艇纺,并重寫(xiě)set方法怎静,當(dāng)set方法調(diào)用時(shí),會(huì)通知觀測(cè)者黔衡;
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
// key為name蚓聘,開(kāi)啟手動(dòng)通知
if ([key isEqualToString:@"name"]) {
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
如果
返回的是NO;那么需要手動(dòng)調(diào)用
,或者
觸發(fā)通知觀測(cè)者
//重寫(xiě)set方法;
- (void)setName:(NSString *)name {
[self willChangeValueForKey:@"name"];
_name = name;
[self didChangeValueForKey:@"name"];
}
kvo底層實(shí)現(xiàn) :isa-swizzling
先創(chuàng)建一個(gè)子類(lèi)盟劫,重寫(xiě)其set方法夜牡,然后修改對(duì)象的isa指針,指向子類(lèi)
// "addObserver:forKeyPath:options:context:"方法的實(shí)現(xiàn)
// 動(dòng)態(tài)創(chuàng)建子類(lèi)
NSString *newName = [@"NSKVONotifying_" stringByAppendingString:NSStringFromClass(object_getClass(self))];
NSString *setterName = [[@"set" stringByAppendingString:[[[keyPath uppercaseString] substringToIndex:1] stringByAppendingString:[keyPath substringFromIndex:1]]] stringByAppendingString:@":"];
Class subCls = objc_allocateClassPair(object_getClass(self), [newName UTF8String], 0);
class_addMethod(subCls, NSSelectorFromString(setterName), (IMP)DefaultSetterForKVO, "v@:@");
objc_registerClassPair(subCls);
/// 替換子類(lèi)的isa
object_setClass(self, subCls);