Runtime是一套底层的C语言API(包含强大的C语言数据类型和函数),RunTime简称运行时。OC就是运行时机制,其中最主要的是消息机制。
苹果和GNU各自维护一个开源的runtime版本
https://github.com/opensource-apple/objc4 (Apple)
消息机制
Runtime进行方法调用本质上是发送消息,通过objc_msgSend()函数进行消息发送,下面就以一个对象的实例化为例
Person *p = [[Person alloc] init];
对于我们平常使用的初始化方法 alloc、 init、new,如果用消息机制分解步骤具体如下:
//通过类名获取类
Class class = objc_getClass("Person");
//注意Class实际上也是对象,所以同样能够接受消息,向Class发送alloc消息
Person *p = objc_msgSend(class,@selector(alloc));
//发送init消息给Person实例p
p = objc_msgSend(p,@selector(init));
//发送run消息给p 即调用run实例方法
objc_msgSend(p, @selector(run));
//汇总传递消息链
objc_msgSend(objc_msgSend(objc_msgSend(objc_getClass("Person"), @selector(alloc)), @selector(init)), @selector(run));
如果只申明了方法编号 ,没有对应的方法实现,在method_array_t的方法列表中没有该方法编号
使用objc_msgSend消息机制,必须导入头文件 @import <objc/messgae..h>,让后配置
Build Setting–>Apple LLVM 6.0 - Preprocessing–>Enable Strict Checking of objc_msgSend Calls 改为 NO
当向一个实例对象发送消息时,对象会根据super class指针找到object_class这个结构体,在这个结构体中,有一个method_array_t存储的方法列表,向对象发送消息就回去
在消息发送过程中,倘若没找到指定消息,runtime为我们预留了三次补救机会method_array_t中找是否有对应方法的实现,如果没有找到,就会沿着继承树去找,直到找到根类, 如果根类也没有对应方法实现,那么接下来就进入了我们常说的消息转发流程
消息转发机制,步骤如下图:
//第一次 动态方法解析
+(BOOL)resolveInstanceMethod:(SEL)sel;
//第二次 快速消息转发
-(id)forwardingTargetForSelector:(SEL)aSelector;
//第三次 标准消息转发 重定向
-(void)forwardInvocation:(NSInvocation *)anInvocation;
//消息中转中心
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
动态修改变量
/** 1.动态修改变量(私有变量也可修改)*/
- (void)dynamicModifyVariable {
NSLog(@"修改前姓名为:%@", [p valueForKey:@"age"]);
unsigned int count = 0;
// 获取类的成员变量列表(包括私有) 获取属性,方法,协议列表 类似
Ivar *varList = class_copyIvarList([Person class], &count);
for (int i = 0; i < count; i++) {
Ivar var = varList[i];
const char *varName = ivar_getName(var);
NSString *proname = [NSString stringWithUTF8String:varName];
NSLog(@"查看成员变量:%@",proname);
if ([proname isEqualToString:@"_age"]) {
object_setIvar(p, var, @"22");
// [p setValue:@"22" forKey:@"age"];
}
}
NSLog(@"修改后姓名为:%@", [p valueForKey:@"age"]);
}
动态添加方法
分类不能增加方法,Extension 可以
//1、注册方法
SEL getInformationSelector = sel_registerName("getPersonAllInfo");
//2、添加方法
class_addMethod([Person class], getInformationSelector, (IMP)getInformation, "v@");
动态拦截与替换方法
拦截系统自带方法的调用(Swizzle黑魔法),也可以理解为替换系统方法
使用场景:比如数据埋点,搜集某些页面进入次数,或者按钮点击次数,在不改变原有结构的前提下,使用拦截方法可以实现无侵入式埋点或者其他功能, 但是方法拦截需要慎重使用,可能牵一发而动全身.
+(void)load {
[super load];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//获取方法名
SEL origSel = @selector(sendAction:to:forEvent:);
SEL swizSel = @selector(ept_sendAction:to:forEvent:);
Method origMethod = class_getInstanceMethod([self class], origSel);
Method swizMethod = class_getInstanceMethod([self class], swizSel);
//判断方法是否添加成功
BOOL addMethod = class_addMethod([self class], origSel, method_getImplementation(swizMethod), method_getTypeEncoding(swizMethod));
if (addMethod) {
//替换类中已有方法的实现,如果该方法不存在添加该方法
class_replaceMethod([self class], swizSel, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
}else{
//替换Method
method_exchangeImplementations(origMethod, swizMethod);
}
});
}
- (void)ept_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
[self ept_sendAction:action to:target forEvent:event];
NSLog(@"拦截系统点击方法成功,调用ept_sendAction");
}
动态拦截替换方法常用函数:
1、class_getInstanceMethod 获取方法名
2、class_addMethod 判断方法是否添加成功
3、class_replaceMethod 替换类中已有方法的实现,如果该方法不存在添加该方法
4、method_exchangeImplementations 替换Method
动态添加属性
// 重写set和get方法 设置关联
- (NSString *)mobile {
return objc_getAssociatedObject(self, "mobile");
}
- (void)setMobile:(NSString *)mobile {
objc_setAssociatedObject(self, "mobile", mobile, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
重写set和get方法,设置关联属性
/*
第一个参数:当前对象
第二个参数:新增属性键值 /
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
/*
第一个参数id object, 当前对象
第二个参数const void key, 关联的key,是c字符串
第三个参数id value, 被关联的对象的值
第四个参数objc_AssociationPolicy policy关联引用的规则
*/
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,id _Nullable value, objc_AssociationPolicy policy)
自动化归解档
归解档遵循NSCoding协议即可,实现归档和解档即可
#pragma mark - coding
- (void)encodeWithCoder:(NSCoder *)aCoder;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder;
对于自定义类型的归解档,最原始的方法就是挨个属性去归档、解档,但是当你要归档的对象永远数亿属性资产的时候,很显然最原始的方法已经无法满足我们对于效率的诉求,从而就有了自动化归解档的诞生
分别在两个方法去遍历成员变量,取值、设值
#pragma mark - coding
- (void)encodeWithCoder:(NSCoder *)aCoder{
/**
传统做法:挨个属性去归档
但是当遇到n多属性的时候 显然一个个去归档就很繁琐了
*/
// [aCoder encodeObject:self.name forKey:@"name"];
// [aCoder encodeObject:self.sex forKey:@"sex"];
// [aCoder encodeObject:self.friends forKey:@"friends"];
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList([Person class], &count);
for (int i = 0; i < count; i ++) {
Ivar ivar = ivarList[i]; // 从成员列表中取出成员变量
const char *name = ivar_getName(ivar); // 获取成员变量名
// 进行归档
NSString *key = [NSString stringWithUTF8String:name];
id value = [self valueForKey:key];
[aCoder encodeObject:value forKey:key];
}
free(ivarList);
}
获取成员变量,赋值成员变量
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder{
self = [super init];
if (self) {
// _name = [aDecoder decodeObjectForKey:@"name"];
// _sex = [aDecoder decodeObjectForKey:@"sex"];
// _friends = [aDecoder decodeObjectForKey:@"friends"];
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList([Person class], &count);
for (int i = 0; i < count; i++) {
Ivar ivar = ivarList[i]; // 从成员列表中取出成员变量
const char *name = ivar_getName(ivar); // 获取成员变量名
// 进行解档
NSString *key = [NSString stringWithUTF8String:name];
id value = [aDecoder decodeObjectForKey:key];
// 将值赋值给成员变量
[self setValue:value forKey:key];
}
free(ivarList);
}
return self;
}