Runtime (运行时),是一套纯C的API。是OC的运行机制。最主要的是消息机制。OC调用函数的本质就是消息发送,是动态的调用过程。只有在真正的运行的时候才会跟具函数的名称找到对应的函数调用。

       消息机制的原理:对象根据方法编号SEL去映射表找到对应的方法实现。

       每个OC方法,底层必然有一个与之对应的runtime方法。

runtime消息机制

OC解决消息机制方法提示步骤(build setting->搜索msg->objc_msgSeng (YES——>NO))。

//    Person *p1=[[Person alloc]init];
    Person *p1=objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"));
    p1=objc_msgSend(p1, sel_registerName("init"));
    //调用对象方法(本质:让对象发送消息)
//    [p1 eat];
    //调用对象方法
//    [p1 performSelector:@selector(eat)];
    // 本质:让类对象发送消息,最后找的是方法实现IMP
    objc_msgSend(p1, @selector(eat));
    //调用传参数的方法
    objc_msgSend(p1, @selector(eatSomthing:),@"参数");
    objc_msgSend(p1, @selector(sendMsg:msg:),@"参数1",@"参数2");

runtime方法调用流程(消息机制)

怎么去掉用run方法,对象方法:(保存到对象的方法列表),类方法:(保存到元类Meta Class方法列表)?

  1. OC在向一个对象发送消息时,runtime库会根据对象的isa指针找到对象对应的类或其父类中查找方法。
  2. 注册方法编号(方法编号的好处,可以快速查找)。
  3. 根据方法编号去查找对应方法。
  4. 找到最终函数实现地址,再根据地址方法去调用对应函数。

注释:每个对象内部都有一个isa指针,这个指真指向它的真实类型,根据这个指针就能知道将来调用哪个类的方法。

runtime常见的作用

  • 动态交换两个方法的实现。
  • 动态添加属性。
  • 实现字典转模型的自动转换。
  • 发送消息。
  • 动态添加方法。
  • 拦截并替换方法。
  • 实现NSCoding的自动归档和解档。

runtime常用的开发应用场景

Runtime 交换方法

       应用场景:第三方框架或者系统的方法不能满足我们的时候,可以在保持系统原有的方法基础上添加额外的功能。

     方法一:继承系统的类,重新方法(弊端,每次要导入)。

     方法二:使用runtime交换方法。

需求:加载一张图片直接使用[UIImage imageName:@“img”];无法知道到底加载成功没有。给系统imageName添加额外的功能(是否加载成功)。

实现步骤:

  • 给系统的方法添加分类
  • 自己实现一个带有扩展功能的方法。
  • 交换方法。只交换一次
#import "UIImage+image.h"
#import <objc/runtime.h>
@implementation UIImage (image)
//load方法: 把类加载进内存的时候调用,只会调用一次,方法应先交换,再去调用
+ (void)load{
    //获取类的方法
    Method method=class_getClassMethod([UIImage class],@selector(imageNamed:));
    Method myMethod =class_getClassMethod(self, @selector(LY_imageWithName:));
    //.交换方法地址,相当于交换实现方式;
    method_exchangeImplementations(method, myMethod);
}
+(id)LY_imageWithName:(NSString *)name{
    UIImage *img=[UIImage LY_imageWithName:name];
    if (img) {
        NSLog(@"加载成功");
    }else{
        NSLog(@"加载失败");
    }
    return img;
}
@end

runtime给分类动态添加属性

原理:给一个类声明属性,其实本质就是给这个类添加关联,并不是直接把这个值得内存空间添加到类内存空间。

应用场景:给系统的类添加属性的时候,可以使用runtime动态添加属性方法。

需求:给NSObject类动态添加属性name字符串。

#import <Foundation/Foundation.h>
@interface NSObject (LYObjc)
@property NSString *name;
@end
#import "NSObject+LYObjc.h"
#import <objc/runtime.h>
@implementation NSObject (LYObjc)
-(void)setName:(NSString *)name{
    // objc_setAssociatedObject(将某个值跟某个对象关联起来,将某个值存储到某个对象中)
    // object:给哪个对象添加属性
    // key:属性名称
    // value:属性值
    // policy:保存策略
    objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSString *)name{
    return objc_getAssociatedObject(self, @"name");
}
@end
//调用引入头文件
NSObject *objc=[[NSObject alloc]init];
    objc.name=@"123";
    NSLog(@"objc.name====%@",objc.name);

总结:给属性赋值的本质,就是让属性与一个对象产生关联,runtime可以做到这一点。

runtime字典转模型

字典转模型的方式

  • 一个一个的给模型属性赋值。
  • 字典转模型KVC实现。
  • KVC字典转模型弊端:必须保证,模型中的属性和字典中的key一一对应。
  • 模型中的属性和字典key不一一对应,系统会调用setValue:forUndefinedKey:报错。
  • 解决:重写对象setValue:forUndefinedKey:把系统方法覆盖,就能继续使用KVC,字典转模型。
  • 字典转模型runtime实现。

runtime其他作用

动态添加方法

应用场景:如果一个类的方法很多,加载类到内存的时候也比较耗费资源,需要给每一个方法生成映射表,可以使用动态给某个类,添加方法解决。

动态变量控制

实现NSCoding的自动归档和解档

Runtime 下Class的各项操作

// 得到类的所有方法
    Method *allMethods = class_copyMethodList([Person class], &count);
    // 得到所有成员变量
    Ivar *allVariables = class_copyIvarList([Person class], &count);
    // 得到所有属性
    objc_property_t *properties = class_copyPropertyList([Person class], &count);
    // 根据名字得到类变量的Ivar指针,但是这个在OC中好像毫无意义
    Ivar oneCVIvar = class_getClassVariable([Person class], name);
    // 根据名字得到实例变量的Ivar指针
    Ivar oneIVIvar = class_getInstanceVariable([Person class], name);
    // 找到后可以直接对私有变量赋值
    object_setIvar(p1, oneIVIvar, @"Mike");//强制修改name属性
    /* 动态添加方法:
     第一个参数表示Class cls 类型;
     第二个参数表示待调用的方法名称;
     第三个参数(IMP)myAddingFunction,IMP是一个函数指针,这里表示指定具体实现方法myAddingFunction;
     第四个参数表方法的参数,0代表没有参数;
     */
    class_addMethod([p1 class], @selector(sayHi), (IMP)myAddingFunction, 0);
    // 交换两个方法
    method_exchangeImplementations(method1, method2);
    // 关联两个对象
    objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
    /*
     id object                     :表示关联者,是一个对象,变量名理所当然也是object
     const void *key               :获取被关联者的索引key
     id value                      :被关联者,这里是一个block
     objc_AssociationPolicy policy : 关联时采用的协议,有assign,retain,copy等协议,一般使用OBJC_ASSOCIATION_RETAIN_NONATOMIC
     */
    
    // 获得某个类的类方法
    Method class_getClassMethod(Class cls , SEL name)
    // 获得某个类的实例对象方法
    Method class_getInstanceMethod(Class cls , SEL name)
    // 交换两个方法的实现
    void method_exchangeImplementations(Method m1 , Method m2)
    // 将某个值跟某个对象关联起来,将某个值存储到某个对象中
    void objc_setAssociatedObject(id object , const void *key ,id value ,objc_AssociationPolicy policy)
    // 利用参数key 将对象object中存储的对应值取出来
    id objc_getAssociatedObject(id object , const void *key)
    // 获得某个类的所有成员变量(outCount 会返回成员变量的总数)
    Ivar *class_copyIvarList(Class cls , unsigned int *outCount)
    // 获得成员变量的名字
    const char *ivar_getName(Ivar v)
    // 获得成员变量的类型
    const char *ivar_getTypeEndcoding(Ivar v)
    // 获取类里面所有方法
    class_copyMethodList(__unsafe_unretained Class cls, unsigned int *outCount)// 本质:创建谁的对象
    // 获取类里面属性
    class_copyPropertyList(__unsafe_unretained Class cls, unsigned int *outCount)

Runtime几个概念

1、objc_msgSend

         这个是最基本的消息发送函数。编译器会根据情况在objc_msgSend、objc_msgSend_stret、objc_msgSendSuper、objc_msgSendSuper_stret四个方法中选择一个来调用。如果消息传递给的是超类,会调用带有Super,如果消息返回的是数据结构而不是简单值时,会调用带有stret函数。

2、SEL

objc_msgSend函数第二个参数类型为SEL,它是selector在Objc中的表示类型,是方法选择器,它的数据结构SEL:typedef struct objc_selector *SEL; 其实它就是映射方法的C字符串。可以使用runtime系统的sel_registerName函数获得一个SEL类型的方法选择器。

3、id

objc_msgSend的第一个参数类型id,是指向类实例的指针:typedef struct objc_object *id;其中objc_object是struct objc_object { Class isa; };是一个结构体包含一个isa指针,根据isa指针找到对象所属的类。

4、runtime.h 里Class的定义

struct objc_class {
//每个Class都有一个isa指针
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
//父类
    Class _Nullable super_class               OBJC2_UNAVAILABLE;
//类名
    const char * _Nonnull name                OBJC2_UNAVAILABLE;
//类版本
    long version                              OBJC2_UNAVAILABLE;
//供运行期使用的一些位标识。如:CLS_CLASS (0x1L)表示该类为普通class; CLS_META(0x2L)表示该类为metaclass等(runtime.h中有详细列出)
    long info                                 OBJC2_UNAVAILABLE;
//实例大小
    long instance_size                        OBJC2_UNAVAILABLE;
//存储每个实例变量的内存地址
    struct objc_ivar_list * _Nullable ivars   OBJC2_UNAVAILABLE;
//根据info的信息确定是类还是实例,运行什么函数方法等
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
//缓存
    struct objc_cache * _Nonnull cache         OBJC2_UNAVAILABLE;
//协议
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif
 
} OBJC2_UNAVAILABLE;

总结:运行时一个类关联了它的超类指针,类名,成员变量,方法,缓存,还有附属的协议

在objc_class结构体中:ivars 是objc_ivar_list指针, methodLists是指向objc_method_list的指针的指针。即可以动态修改*methodLists的值来添加成员方法,这也是Category实现的原理。

什么是method swizzling(俗称黑魔法)

  • 简单说就是进行方法交换。
  • 在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的。
  • 每个类都有一个方法列表,存放着方法的名字和方法实现的映射关系,selector的本质其实就是方法名,IMP有点类似函数指针,指向具体的Method实现,通过selector就可以找到对应的IMP。

交换方法的几种实现:

  • 利用 method_exchangeImplementations 交换两个方法的实现。
  • 利用 class_replaceMethod 替换方法的实现。
  • 利用 method_setImplementation 来直接设置某个方法的IMP。