现在看起来Runtime篇整理的少了,有时间再完善下,将就着看吧
什么是Runtime?
Objective-C将很多静态语言在编译和链接时期做的工作放在了Runtime运行时处理,可以说Runtime就是Objective-C的幕后工作者。
- Runtime(简称运行时),是一套由纯C写的API。
- 对于C语言,函数的调用会在编译的时候决定调用哪个函数。
- OC中的函数调用成为 消息发送 ,属于动态调用过程。在编译的时候并不能真正决定调用那个函数,只有真正运行的时候才会根据函数名称找到对应的函数来调用。
- 事实证明:在编译阶段,OC可以调用任意函数,即使这个函数并未实现,只有声明过就不会报错,只有运行时才会报错,这是因为OC是动态调用的。而C语言调用未实现的函数就会报错。
消息机制
任何方法调用的本质,就是发送了一个消息(用Runtime发送消息,OC底层实现通过Runtime实现)。
- 原理
对象根据方法编号SEL去隐射表查找对应的方法实现。
- 方法调用流程
- OC在向一个对象发送消息时,Runtime会根据该对象的isa指针找到该对象对应的类或者父类。
- 根据编号SEL在Method_List中查找对应方法。
- 如果找到最终函数实现地址,根据地址去方法区调用对应函数。如果没找到,会有三次拯救机会,否则抛出异常。
- Method resolution:objc运行时会调用+resolveInstanceMethod:或者 +resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数,那运行时系统就会重新启动一次消息发送的过程,否则 ,运行时就会移到下一步,消息转发(Message Forwarding)。
- Fast forwarding:如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。 只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会继续Normal Fowarding。 这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但下一步转发会创建一个NSInvocation对象,所以相对更快点。
- Normal forwarding:这一步是Runtime最后一次给你挽救的机会。首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。 PS:对象方法保存在类对象的方法列表中,类方法保存在元类的方法列表中。
常用场景
- 交换方法
有时候我们需要对类的方法进行修改,但是又无法拿到源码,我们便可以通过Runtime来交换方法实现。
+ (void)load {
//获取实例方法实现
Method method1 = class_getInstanceMethod(self, @selector(show));
Method method2 = class_getInstanceMethod(self, @selector(ln_show));
//获取类方法实现
// Method method3 = class_getClassMethod(self, @selector(show));
// Method method4 = class_getClassMethod(self, @selector(ln_show));
//交换两个方法的实现
method_exchangeImplementations(method1, method2);
//将method1的实现换成method2
// method_setImplementation(method1, method_getImplementation(method2));
}
- (void)show {
NSLog(@"show person");
}
- (void)ln_show {
NSLog(@"show person exchange");
}
- 添加属性
实际上并没有产生真正的成员变量,通过关联对象来实现,具体参考分类。
- 字典转模型
除了可以使用KVC实现外,还可以通过Runtime实现,就是取出所有ivars遍历赋值。但实际情况一般比较复杂:
- 当字典的key和模型的属性匹配不上。
- 模型中嵌套模型(模型属性是另外一个模型对象)。
- 数组中装着模型(模型的属性是一个数组,数组中是一个个模型对象)。 我们这里仅考虑最简单的情况
+ (instancetype)modelWithDic:(NSDictionary *)dic {
/*
1.初始化实例对象
*/
id object = [[self alloc] init];
/**
2.获取ivars
class_copyIvarList: 获取类中的所有成员变量
Ivar:成员变量
第一个参数:表示获取哪个类中的成员变量
第二个参数:表示这个类有多少成员变量,传入一个Int变量地址,会自动给这个变量赋值
返回值Ivar *:指的是一个ivar数组,会把所有成员属性放在一个数组中,通过返回的数组就能全部获取到。
count: 成员变量个数
*/
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList(self, &count);
/*
3.遍历赋值
*/
for(int i = 0; i < count; i++) {
//获取ivar属性
Ivar ivar = ivarList[i];
//获取属性名
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
//去掉成员变量的下划线
NSString *key = [ivarName substringFromIndex:1];
//获取dic中对应值
id value = dic[ivarName];
//如果值存在,则赋值
if(value) {
[object setValue:value forKey:ivarName];
}
}
return object;
}
- 动态添加方法
如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决。
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [[Person alloc] init];
// 默认person,没有实现run:方法,可以通过performSelector调用,但是会报错。
// 动态添加方法就不会报错
[p performSelector:@selector(run:) withObject:@10];
}
@implementation Person
// 没有返回值,1个参数
// void,(id,SEL)
void aaa(id self, SEL _cmd, NSNumber *meter) {
NSLog(@"跑了%@米", meter);
}
// 任何方法默认都有两个隐式参数,self,_cmd(当前方法的方法编号)
// 什么时候调用:只要一个对象调用了一个未实现的方法就会调用这个方法,进行处理
// 作用:动态添加方法,处理未实现
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
// [NSStringFromSelector(sel) isEqualToString:@"run"];
if (sel == NSSelectorFromString(@"run:")) {
// 动态添加run方法
// class: 给哪个类添加方法
// SEL: 添加哪个方法,即添加方法的方法编号
// IMP: 方法实现 => 函数 => 函数入口 => 函数名(添加方法的函数实现(函数地址))
// type: 方法类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
class_addMethod(self, sel, (IMP)aaa, "v@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
- NSCoding的自动归档和解档
在实现encodeObject
和decodeObjectForKey
方法中,我们一般需要把每个属性都要写一遍,这样很麻烦,我们可以通过Runtime来自动化。
- (void)encodeWithCoder:(NSCoder *)aCoder {
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList([self class], &count);
for(int i = 0; i < count; i++) {
Ivar ivar = ivarList[i];
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
id value = [self valueForKey:ivarName];
[aCoder encodeObject:value forKey:ivarName];
}
free(ivarList);
}
- (id)initWithCoder:(NSCoder *)aDecoder {
if(self == [super init]) {
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList([self class], &count);
for(int i = 0; i < count; i++) {
Ivar ivar = ivarList[i];
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
id value = [aDecoder decodeObjectForKey:ivarName];
[self setValue:value forKey:ivarName];
}
free(ivarList);
}
return self;
}
还有更简便的方法,抽象成宏,参考网上资料。
- 常用API
unsigned int count = 0;
//获取属性列表
Ivar *propertyList = class_copyPropertyList([self class], &count);
//获取方法列表
Method *methodList = class_copyMethodList([self class], &count);
//获取成员变量列表
Ivar *ivarList = class_copyIvarList([self class], &count);
//获取协议列表
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
//获取类方法
Method method1 = class_getClassMethod([self class], @selector(run));
//获取实例方法
Method method2 = class_getInstanceMethod([self class], @selector(tempRun));
//添加方法
class_addMethod([self class], @selector(run), method_getImplementation(method2), method_getTypeEncoding(method2));
//替换方法
class_replaceMethod;
//交换方法
method_exchangeImplementations;
load与initialize
- load
当类被引进项目的时候会执行load函数(在main函数开始之前),与这个类是会被用到无关,每个类的load函数只会被调用一次。由于load函数是自动加载的,不需要调用父类的load函数。
- 当父类和子类都实现了load函数时,先调用父类再调用子类。
- 当子类未实现load方法时,不会调用父类的load方法。
- 类中的load执行顺序要优于分类。
- 多个类别都有load方法时,其执行顺序与分类中其他相同方法一样,根据编译顺序决定。
- initialize
这个方法会在类接收到第一次消息时调用。由于是系统调用,也不需要调用父类方法。
- 父类的initialize方法比子类优先。
- 当子类未实现initialize方法,会调用父类initialize方法;子类实现initialize方法时,会重载initialize方法。
- 当多个分类都实现了initialize方法,会执行最后一个编译的分类中的initialize方法。