什么是runtime
runtime是底层的纯C语言的API,它包含的很多底层的语法。
我们平时编写的OC代码,在程序运行过程中,最终都是转化成了runtime的C语言代码,
runtime也称为运行时,它是OC的幕后工作者。
runtime的作用
runtime主要就是做一些底层的操作,如:
1. 动态的添加对象的成员变量和方法
2.动态交换两个方法的实现(可以替换系统的方法)
3.获得某个类的所有成员方法、所有成员变量
4. 实现分类也可以添加属性
5.实现NSCoding的自动归档和解档
6.实现字典转模型的自动转换
替换系统方法,可以通过拦截系统的方法探究底层,比如block 的实现原理
常用方法
1.获取类中的方法
Method class_getClassMethod(Method cls , SEL name)
如:
Method m = class_getClassMethod([Person class],@selector(setName:));
2.获取对象中的方法
Method class_getInstanceMethod(Method cls, SEL name)
如:
Person *person = [[Person alloc] init];
Method m = get_InstanceMethod([person class],@selector(setName:));
3.交换两个方法的实现
void method_exchangeImplementations(Method m1,Method m2)
如
Person *p =[[Person alloc] init];
[p study];
[p run];
//交换实现
//instance method :实例方法,
//class_getInstanceMethod得到实例的方法(即对象方法)
//两个参数 1:类名 2.方法名
//class_getClassMethod :得到实例化的方法
Method m1 = class_getInstanceMethod([Person class], @selector(study));
Method m2 = class_getInstanceMethod([Person class], @selector(run));
method_exchangeImplementations(m2, m1);
[p study];
[p run];
具体操作
051B23EE-AFC6-4C95-9297-1E58708D5B96.png
4.获取成员变量
Ivar *ivars = class_getCopyIvarList(Ivar ivar);
实现分类中添加属性
为所有的NSObject对象添加属性
1.首先创建一个NSObject分类NSObject+Extension
2.在.h中使用@property添加属性
此时使用@property添加数属性,并非真正的属性,如果此时调用查看属性,将会崩溃,
因为分类并未实现添加添加属性的功能,想要添加属性,需要使用runtime,动态的添加
3.在.m文件中实现getter和setter方法
如果想要添加多个属性,就需要在每个对象中抽出一块空间用于存放属性,
使用objc_setAssociatedObject方法进行关联
#import "NSObject+Extension.h"
#import <objc/runtime.h>
@implementation NSObject (Extension)
//用于存放属性的变量,多个属性,需要创建不同的变量
char BookKey;
-(void)setBooks:(NSArray *)books{
objc_setAssociatedObject(self, &BookKey, books, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSArray *)books{
return objc_getAssociatedObject(self, &BookKey);
}
@end
遵守协议NSCoding,实现属性的自动归档与解档
需求分析:
当想要对象自动进行归档解档的时候,如果属性非常的多,一个一个天添加[encoder encodeObject:@(xxx) forKey:@"_xxx"];将会非常的繁琐。
既然能够获取所有的属性,我们就可以通过循环遍历属性的方式进行统一的归档和解档
- (id)initWithCoder:(NSCoder *)decoder
{
if (self = [super init]) {
// 用来存储成员变量的数量
unsigned int outCount = 0;
// 获得Dog类的所有成员变量
Ivar *ivars = class_copyIvarList([self class], &outCount);
// 遍历所有的成员变量
for (int i = 0; i<outCount; i++) {
// 取出i位置对应的成员变量
Ivar ivar = ivars[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 获得key对应的值
id value = [decoder decodeObjectForKey:key];
// 设置到成员变量上
[self setValue:value forKeyPath:key];
}
free(ivars);
}
return self;
}
/**
* 将对象写入文件时会调用这个方法(开发者需要在这个方法中说明需要存储哪些属性)
*/
- (void)encodeWithCoder:(NSCoder *)encoder
{
// 用来存储成员变量的数量
unsigned int outCount = 0;
// 获得Dog类的所有成员变量
Ivar *ivars = class_copyIvarList([self class], &outCount);
// 遍历所有的成员变量
for (int i = 0; i<outCount; i++) {
// 取出i位置对应的成员变量
Ivar ivar = ivars[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 通过key获得对应成员变量的值
id value = [self valueForKeyPath:key];
[encoder encodeObject:value forKey:key];
}
free(ivars);
}
注意:
ARC的内存管理机制 只适合OC语法,对于C语言的内存还是需要手动的释放,当使用runtime的时候,
如果包含了copy、create、retain、new等词语,那么在最后就需要释放内存
使用free(对象)进行释放如:free(ivars);
利用runtime实现字典转模型
描述:
KVC的字典转模型具有一个缺陷,就是属性的数量与名称都必须保持一致,如果字典中的属性多,
而模型中没有使用KVC赋值的时候就会崩溃,需要实现另一个方法
setValue:forUndefinedKey:方法,并如果对象中包含了另一个对象作为属性,
也将不能自动将其转化为模型
而使用runtime实现的字典转模型,可以实现将所有的对象都转化为对应的模型,
并且不会出现属性找不到,而奔溃的现象
NSObject+Extension.h
#import <Foundation/Foundation.h>
@interface NSObject (Extension)
-(void)setDiction:(NSDictionary *)dict;
+(instancetype)objectWithDiction:(NSDictionary *)dict;
@end
NSObject+Extension.m
#import "NSObject+Extension.h"
#import <objc/runtime.h>
@implementation NSObject (Extension)
-(void)setDiction:(NSDictionary *)dict{
//获取类
Class c = self.class;
//循环遍历 类 (本类 和所有的父类)
while (c && c != [NSObject class]) {
//获取所有的属性
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList(c, &outCount);
//遍历类中的属性
for (int i = 0; i < outCount; i++) {
//获取属性名
Ivar ivar = ivars[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
//去掉key中的 _
key = [key substringFromIndex:1];
//通过key 获取属性的值
id value = dict[key];
//如果key是一个空值 退出本轮的循环
//原因:如果字典中没有这个key,那么value将会是一个空值,kvc 不能赋值空值
if (value == nil) {
continue;
}
//如果类中包含另一个类为对象,也要将该对象进行字典转模型
//获取对象的属性的类名
NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
// 对象名会以@“名字”的形式 出现,但是同时字符串也是以这种形式表示,因此可以先判断type中是否包含 @ 符号
NSRange range = [type rangeOfString:@"@"];
//如果range.location 不等于NSNotFound说明 找到了@
if (range.location != NSNotFound) {
//截取type中的名字 去除@“ ”
type = [type substringWithRange:NSMakeRange(2, type.length -3)];
if (![type hasPrefix:@"NS"]) {
//将type转化为类名
Class class = NSClassFromString(type);
value = [class objectWithDiction:value];
}
}
//赋值
[self setValue:value forKey:key];
}
//ARC 只适用于OC语法,C语言中的内存 需要手动释放
free(ivars);
c = [c superclass];
NSLog(@"1");
}
}
+(instancetype)objectWithDiction:(NSDictionary *)dict{
NSObject *obj = [[self alloc] init];
[obj setDiction:dict];
return obj;
}
@end
网友评论