runtime

runtime是一套底层的C语言API(包含很多强大实用的C语言数据类型、C语言函数)
运行时最主要的消息机制,是使用c语言给对象发送消息,对象接收到消息后, 找到匹配的方法执行。

常见的函数、头文件

1
2
3
4
5
6
#import <objc/runtime.h> : 成员变量、类、方法
Ivar * class_copyIvarList : 获得某个类内部的所有成员变量
Method * class_copyMethodList : 获得某个类内部的所有方法
Method class_getInstanceMethod : 获得某个实例方法
Method class_getClassMethod : 获得某个类方法
method_exchangeImplementations : 交换2个方法的具体实现

应用场景1:字典转模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
NSDictionary *dict =@{@"name":@"普京",@"age":@18,@"score":@59};
// 1、搜索类里的成员变量
unsigned int count = 0;
// 获取这个类所有的成员变量
Ivar *Ivars = class_copyIvarList([XRPerson class], &count);
XRPerson *person = [[XRPerson alloc]init];
for (int i = 0; i<count; i++) {
Ivar ivar =Ivars[i];
const char *type =ivar_getTypeEncoding(ivar);//获取成员变量的类型,这里没用到
const char *name = ivar_getName(ivar);
// 2、C语言转OC,去掉成员变量的下划线"_"
NSMutableString *strM =[[NSMutableString alloc]initWithString:[NSString stringWithUTF8String:name]];
NSString *resultStr =[strM substringFromIndex:1];
// 3、通过KVC为成员变量赋值
[person setValue:dict[resultStr] forKey:resultStr];
}
NSLog(@"%@ %d %.2lf",person.name,person.age,person.score);

应用场景2:归档解档

当你想把某个类里的所有成员变量拿出来做一些事情,或者成员变量非常多,你就可以利用运行时一次性的对这个类的所有成员变量进行归档解档。

1
2
3
4
5
6
7
8
9
10
- (void)encodeWithCoder:(NSCoder *)encoder{
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([XRPerson class], &count);
for (int i = 0; i<count; i++) {
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
NSString *key = [NSString stringWithUTF8String:name];
[encoder encodeObject:[self valueForKeyPath:key] forKey:key];
}
}

应用场景3:交换2个方法的实现(Method Swizzle)

每个类都维护一个方法(Method)列表,Method则包含SEL和其对应IMP的信息,方法交换做的事情就是把SEL和IMP的对应关系断开,并和新的IMP生成对应关系。
method_exchangeImplementations 交换2个方法的中的实现
class_replaceMethod 修改类
method_setImplementation 直接设置某个方法的实现
class_getInstanceMethod 获取通过SEL获取一个方法
method_getTypeEncoding 获取一个OC实现的编码类型
class_addMethod 給方法添加实现

1、当你想对系统的方法做一些手脚,添加或者修改些什么的时候,就可以用runtime交换2方法的实现

2、旧项目用的是imageNamed方法通过简单判断来适配图片的,图片多了,重复代码很垃圾,难更改,难维护。
解决方案:利用运行时交换两个方法的实现。写一个image分类,用分类写的方法与系统的方法实现交换了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#import <objc/runtime.h>
@implementation UIImage (Extension)
+(void)load{ // 只要分类被装载到内存中,就会调用1次
Method originMethod = class_getClassMethod(self, @selector(imageNamed:));
Method otherMethod = class_getClassMethod(self, @selector(imageWithName:));
// 交换2个方法的实现
method_exchangeImplementations(originMethod, otherMethod);
}
+(UIImage *)imageWithName:(NSString *)name{
BOOL iOS8 = [[UIDevice currentDevice].systemVersion floatValue]>=8.0;
UIImage *image =nil;
if (iOS8) {
NSString *newName=[name stringByAppendingString:@"_os8"];
image=[UIImage imageWithName:newName];
}
if (image==nil) {
image =[UIImage imageWithName:name];
}
return image;
}

4、屏蔽数组传nil报错,数组越界报错(负面效果是难找到报错,谨慎实用,看场合,权衡使用)
解决方案:利用运行时的交换方法的实现的办法
(1)只要外面传nil,我在内部就不管它,
(2)或者自己打印出来哪里传了nil,这样我们就能发现错在哪,要不然找不到报错的地方。
(3)还可以在里面做一些自己想做的事,过滤一些东西,比如:只有传的是string类型的,我才添加到数组中。

5、屏蔽字典传空错误

注意:多个有继承关系的类对象swizzle时,先从父对象开始,这样才能保证子类方法拿到父类中的被swizzle的实现。在+(viod)load中swizzle不会出错,就是因为load类方法会默认从父类开始调用。

打赏支持一下呗!