Runtime的基本使用

没怎么用过运行时,做毕设的时候拿来改了一下textfield的光标颜色,了解实在有限。现在来了解一下除简单获取私有变量的其它综合用途。

Runtime简介

按国际惯例,背书

  • 运行时是一种面向对象的编程语言环境,类似于java的虚拟机
  • OC最主要的特点就是在程序运行的时候,以发送消息的方式调用方法
  • 运行时是OC的核心、底层,OC就是基于运行时的
  • 日常工作中,主要应用场景是关联对象、可以给分类动态的添加属性;动态的获取类的属性,用于字典转模型
  • 交叉方法,在无法修改系统的或者第三方框架的方式时,利用交叉方法,先执行自己的方法,在交换执行三方框架的方法,

具体功能实现

动态获取类的属性列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
+ (NSArray *)xd_objPropertyArr {
/**
获取属性列表
1.要获取的类
2.类属性的个数指针
返回值:所有的属性数组 C语言中,数组的名字,就是指向第一个元素的地址
在OC 中使用C的时候晕倒 retain/create/copy 等 需要release
*/
unsigned int count = 0;
//C语言数组 需要 * 符号
objc_property_t *proArr = class_copyPropertyList([self class], &count);
NSLog(@"属性的数量%d",count);
//创建数组
NSMutableArray *MArr = [NSMutableArray array];
//遍历所有的属性
for (unsigned int i = 0; i < count; i++) {
//从数组中取得属性
// C语言结构体指针,不要 *
objc_property_t pty = proArr[i];
//从pty 中获取属性的名称
const char *cName = property_getName(pty);
NSString *name = [NSString stringWithCString:cName encoding:NSUTF8StringEncoding];
//添加到数组
[MArr addObject:name];
}
//释放数组
free(proArr);

return MArr.copy;
}

字典转模型

模型初始化的时候可以用,如果返回的json很大,不是所有模型都需要这么多的元素,这时候下面的方法就派上用场了,当然还有很多其它的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
+ (instancetype)xd_objWithDic:(NSDictionary *)dic {
//实例化对象
id object = [[self alloc] init];
//使用字典使用对象信息
//获取self 是属性列表
NSArray *proArr = [self xd_objPropertyArr];
//遍历字典的方法
[dic enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
NSLog(@"key %@, value %@",key,obj);
//判断key 是否在proArr中
if ([proArr containsObject:key]) {
//属性存在KVC 赋值
[object setValue:obj forKey:key];
}
}];
return object;
}

归档

如果需要归档的属性很多,写initWithCoder:和encoderWithCoder:这两个方法会很枯燥,那么利用Runtime也能较好地这个问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@implementation Person
//解码
- (id)initWithCoder:(NSCoder *)coder
{
unsigned int iVarCount = 0;
Ivar *iVarList = class_copyIvarList([self class], &iVarCount);//取得变量列表,[self class]表示对自身类进行操作
for (int i = 0; i < iVarCount; i++) {
Ivar var = *(iVarList + i);
const char * varName = ivar_getName(var);//取得变量名字,将作为key
NSString *key = [NSString stringWithUTF8String:varName];
//decode
id value = [coder decodeObjectForKey:key];//解码
if (value) {
[self setValue:value forKey:key];//使用KVC强制写入到对象中
}
}
free(iVarList);//记得释放内存
return self;
}
//编码
- (void)encodeWithCoder:(NSCoder *)coder
{
unsigned int varCount = 0;
Ivar *ivarList = class_copyIvarList([self class], &varCount);
for (int i = 0; i < varCount; i++) {
Ivar var = *(ivarList + i);
const char *varName = ivar_getName(var);
NSString *key = [NSString stringWithUTF8String:varName];
id varValue = [self valueForKey:key];//使用KVC获取key对应的变量值
if (varValue) {
[coder encodeObject:varValue forKey:key];
}
}
free(ivarList);
}

交叉?混合?替换?方法

这是我目前发现Runtime最厉害的地方,网上说这是什么黑魔法?利用这特性可以很方便给原有的方法加入、修改、替换、混合业务逻辑而不修改原来的类文件。
在分类中进行编辑

1
2
3
4
5
6
7
8
9
10
11
+ (void)load {
Method originalMethod = class_getInstanceMethod([self class], @selector(viewWillAppear:));
Method swizzledMethod = class_getInstanceMethod([self class], @selector(xd_viewWillAppear:));
method_exchangeImplementations(originalMethod, swizzledMethod);
}
- (void)xd_viewWillAppear:(BOOL)animated {

NSLog(@"搞事");
//这里实际调用的是原来的viewWillAppear:
[self xd_viewWillAppear:YES];
}

后面会继续学习埋点和归档的优化