荐 iOS:RunTime基本使用与实际运用
大家好!我是OB。今天来聊聊runtime!
一、RunTime是什么?
定义:RunTime实际上是一个库,这个库使我们可以在程序运行时动态的创建对象、检查对象,修改类和对象的方法。他的作用其实就是在程序运行时做一些事情。
下面我们来看看它的常用方法,前提引入头文件 #import <objc/message.h>
,再去【XCode】->【Build Settings】 -> 【Enable Strict Checking of objc_msgSend Calls】
这个字段设置为 NO(默认为YES),防止编译器校验语法。
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:动态添加方法
注意使用performSelector: 来调用,因为对象在编译阶段是没有addMethodTest方法的。防止编译不过
//方法一:
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:动态添加属性
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, "key_name", name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
return (NSString *)objc_getAssociatedObject(self, "key_name");
}
4:遍历属性
注意:通过objc_setAssociatedObject 添加的属性,是不能在这里发现的
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检测
当对象调用没有实现的方法,即在程序运行时,Runtime 会监测程序发生的意外,如果有crash发生,出于对用户的体验考虑,runtime允许在即将crash前,让开发者做一些事情去挽救APP,避免crash发生。其中有三个阶段可以处理crash。
实际开发中没有人傻到会去调用没有实现的方法,如果有当我没说[滑稽]!,
但是当项目大了之后,解析数据,版本字段变更,升级后缓存未清理,都有可能发生 unrecognized selector sent to…错误。
这时可以就可以考虑以下解决方案了!
方案A:Method resolution-方法解析,解决
注意:添加方法时:class_addMethod中,实例方法是[self class],类方法是objc_getMetaClass(“ViewController”), 因为类方法最终查找方法实现是去对象的元类对象中查找,所以添加也是去元类对象中添加
- (void)viewDidLoad {
[super viewDidLoad];
//类方法
[ViewController performSelector:@selector(noMethod)];
//实例方法
[self performSelector:@selector(noMethod)];
}
- (void)test {
NSLog(@"拦截到 %@ crash",NSStringFromSelector(_cmd));
}
//实例方法
+ (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];
}
//类方法
+ (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-消息快速转发
这一步比较简单,就是找一个能实现该方法的对象,让这个对象去实现该方法;
- (id)forwardingTargetForSelector:(SEL)aSelector {
if ([[Person new] respondsToSelector:aSelector]) {
return [Person new];
}
return [super forwardingTargetForSelector:aSelector];
}
方案C:Forwarding-消息慢转发
当然这里的慢是相对于方案B的,不是说真的就是很慢。
- (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];
}
}
注意:以上三个步骤,效率上 A > B >
C,由于对象会缓存方法,如果方案A实现了,addMethod,那么下次调用该方法时,会直接从对象的method cache
里面直接调用,效率更高。A,B,C都实现了,只会执行A!
2:实现NSDictionary 转 Model
就是利用runtime 遍历model实例对象的属性,然后通过KVC给这个属性赋值。[instance setValue:@"value" forKey:property];
3:Category中添加属性
4:通用埋点
利用runtime的方法交换,监听ViewController的停留时长,还能做到无侵入式。Hook生命周期的方法,然后交换方法实现,然后在自定义的方法中,调用外部方法,并
invoke 原来生命周期的方法。
详情请点击埋点专题
5:KVO
kvo底层也是通过runtime来实现的,当某个对象设置kvo时,runtime会在运行时,动态的创建该类的子类,并重写set方法,当set方法调用时,会通知观测者;
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
// key为name,开启手动通知
if ([key isEqualToString:@"name"]) {
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
如果
automaticallyNotifiesObserversForKey
返回的是NO;那么需要手动调用willChangeValueForKey
,或者didChangeValueForKey
触发通知观测者
//重写set方法;
- (void)setName:(NSString *)name {
[self willChangeValueForKey:@"name"];
_name = name;
[self didChangeValueForKey:@"name"];
}
本文地址:https://blog.csdn.net/pk_sir/article/details/107037263