一、什么是Runtime
Runtime是纯C的API,我们编写的OC代码最终都会转化成Runtime的C代码执行。举个简单的例子[target doSomething];
,这是我们平常最常用的调用方法的形式,实际上最终会转化为如下C的代码objc_msSend(target,@selector(doSomething))
(也就是我们所说的消息机制)。
我们知道,OC是面向对象的语言,在OC中可以说一切皆对象。类通过实例化就得到了对象,实际上类本身也是一个对象,在runtime中用结构体表示:
//类在runtime中的表示
struct objc_class {
Class isa;//指针,顾名思义,表示是一个什么,
//实例的isa指向类对象,类对象的isa指向元类
#if !__OBJC2__
Class super_class; //指向父类
const char *name; //类名
long version;
long info;
long instance_size
struct objc_ivar_list *ivars //成员变量列表
struct objc_method_list **methodLists; //方法列表
struct objc_cache *cache;//缓存
//一种优化,调用过的方法存入缓存列表,下次调用先找缓存
struct objc_protocol_list *protocols //协议列表
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
其中下面几个属性,使我们运用runtime时可能会用到的:
/// 描述类中的一个方法
typedef struct objc_method *Method;
/// 成员变量
typedef struct objc_ivar *Ivar;
/// 类别Category
typedef struct objc_category *Category;
/// 类中声明的属性
typedef struct objc_property *objc_property_t;
二、获取列表
有时我们会有获取类中属性名字的需求,例如在字典转模型时,我们就需要知道当前类中每个属性的名字。我们可以通过runtime的一系列方法获取类的一些信息(包括属性列表,方法列表,成员变量列表,和遵循的协议列表)。
//1.获取成员变量列表
unsigned int count1 = 0;
Ivar *ivarList = class_copyIvarList([Student class], &count1);
for (int i = 0; i < count1; i++) {
const char *ivar = ivar_getName(ivarList[i]);
NSString *ivarName = [NSString stringWithUTF8String:ivar];
// NSLog(@"%@",ivarName);
}
//2.获取属性列表
unsigned int count2 = 0;
objc_property_t *propertyList = class_copyPropertyList([Student class], &count2);
for (int i = 0; i < count2; i++) {
const char *property = property_getName(propertyList[i]);
NSString *propertyName = [NSString stringWithUTF8String:property];
// NSLog(@"--->%@",propertyName);
}
//3.获取方法列表
unsigned int count3 = 0;
Method *methodList = class_copyMethodList([Student class], &count3);
for (int i = 0; i < count3; i++) {
SEL sel = method_getName(methodList[i]);
NSString *selName = NSStringFromSelector(sel);
// NSLog(@"+++++%@",selName);
}
//4.获取协议列表
unsigned int count4 = 0;
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([Student class], &count4);
for (int i = 0; i < count4; i++) {
const char *pro = property_getName((__bridge objc_property_t)(protocolList[i]));
NSString *protocolName = [NSString stringWithUTF8String:pro];
NSLog(@"%@",protocolName);
}
三、方法调用
如果用实例对象调用实例方法,会到实例的isa指针指向的对象(也就是类对象)操作。
如果调用的是类方法,就会到类对象的isa指针指向的对象(也就是元类对象)中操作。
1. 首先,在相应操作的对象中的缓存方法列表中找调用的方法,如果找到,转向相应实现并执行。
2. 如果没找到,在相应操作的对象中的方法列表中找调用的方法,如果找到,转向相应实现执行
3. 如果没找到,去父类指针所指向的对象中执行1,2.
4. 以此类推,如果一直到根类还没找到,转向拦截调用。
5. 如果没有重写拦截调用的方法,程序报错。
四、runtime在快速归档中的应用
#import "Student.h"
@interface Student()<NSCoding>
@end
@implementation Student
/** 归档 */
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeObject:@(self.age) forKey:@"age"];
[aCoder encodeObject:@(self.height) forKey:@"height"];
}
/** 解档 */
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
self.name = [aDecoder decodeObjectForKey:@"name"];
self.age = [[aDecoder decodeObjectForKey:@"age"] integerValue];
self.height = [[aDecoder decodeObjectForKey:@"height"] floatValue];
}
return self;
}
@end
#import "ViewController.h"
#import "Student.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Student *student = [[Student alloc] init];
student.name = @"刘亦菲";
student.age = 30;
student.height = 170.0;
/** 归档 */
// [NSKeyedArchiver archiveRootObject:student toFile:@"/Users/liuliuhaiqiang/Desktop/student.data"];
/** 解档 */
Student *newStudent = [NSKeyedUnarchiver unarchiveObjectWithData:[NSData dataWithContentsOfFile:@"/Users/liuliuhaiqiang/Desktop/student.data"]];
NSLog(@"姓名:%@,年龄:%ld,身高:%f",newStudent.name,(long)newStudent.age,newStudent.height);
}
@end
按照这样普通的方法,确实也能够归档。但是如果出现以下两种情况,用这种常规的方法进行归档就显得效率较低。一是类的属性较多是,二是类的属性发生改变时。这时使用runtime进行快速归档就能很好的解决以上问题。
/** 归档 */
- (void)encodeWithCoder:(NSCoder *)aCoder {
unsigned int count = 0;
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (int i = 0; i < count; i++) {
const char *property = property_getName(propertyList[i]);
NSString *propertyName = [NSString stringWithUTF8String:property];
//先取值再归档
id value = [self valueForKey:propertyName];
[aCoder encodeObject:value forKey:propertyName];
}
}
/** 接档 */
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
unsigned int count = 0;
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (int i = 0; i < count; i++) {
const char *property = property_getName(propertyList[i]);
NSString *propertyName = [NSString stringWithUTF8String:property];
//先解档在赋值
id value = [aDecoder decodeObjectForKey:propertyName];
[self setValue:value forKey:propertyName];
}
}
return self;
}
使用runtime后无论属性有多少,增加多少属性,均无需修改密码!