一、什么是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后无论属性有多少,增加多少属性,均无需修改密码!