自学内容网 自学内容网

Runtime

什么是Runtime机制?

iOS中的Runtime机制是OC和Swift的一种动态特性,它允许应用在运行时动态地创建类对象和实例对象,并通过消息传递和转发来实现方法的调用。

静态语言 VS动态语言

OC和Swift最大的区别就在于,OC是动态语言,而Swift是静态语言,对于静态语言来说,在编译时,编译器就已经确定了变量的类型和函数的调用地址,通过代码中写的函数名就可以直接与内存中的函数地址相关联。而对于动态语言来说,在运行时才会确定数据类型和结构,函数调用也不能直接通过函数名绑定到函数地址,而是通过消息传递机制来实现的,编译时并不知道具体调用什么方法,而是在程序运行时才会通过Runtime机制来查找调用方法。

Runtime的优点:

  • 灵活性:利用Runtime,你可以在运行时动态的修改类或对象的行为,比如方法的替换,属性和方法的动态添加。
  • 支持动态特性:KVO、消息转发、反射等功能都依赖Runtime机制。

消息机制原理

在OC语言中,对象方法的调用都是类似[receiver selector]的形式,其本质是让对象在运行时发送消息的过程。

那么方法调用[receiver selector]在编译阶段和运行阶段都做什么呢?

编译阶段:

[receiver selector]方法会被编译器转化成以下两种情况:

  1. objc_msgSend(receiver,selector) (不带参数)
  2. objc_msgSend(recevier,selector,org1,org2,…)(带参数)

上面被转换的objc_msgSend是一种被称为消息发送的C函数,它的第一个参数表示消息的接受者,第二个参数是方法的标识符,标识需要调用那个具体的方法。

在这个编译过程中并不会直接调用具体的函数。

运行阶段:根据接受者receiver找到对应的selector。过程如下:

  • 通过receiver的isa指针找到receiver的Class类。

每一个receiver都会有一个isa指针,他指向类的描述信息,对于类的实例对象,它的isa指针指向的就是对应的类或类对象,对于类对象,它的isa指针指向的是元类对象,对于元类对象,它的isa指针指向根元类对象。

  • 在Class的缓存中查找相应方法

OC方法在第一次调用之后会将方法添加到缓存中,以此提高查找速度

  • 如果缓存中没有找到,就在方法列表中查找,并将方法添加到缓存。
  • 如果方法列表中没有找到,则沿着该对象所属类的继承体系继续向上查找
  • 如果还是找不到,则会启动”消息转发“操作。

例如:

@interface Person : NSObject
- (void)eat;
@end
 
@implementation Person
- (void)sayHello {
    NSLog(@"I`m eating");
}
@end
 
Person *p = [[Person alloc] init];
[p eat];

在执行[p eat]这段代码中,在编译阶段会被编译成:objc_msgSend(p,@selector(eat))

在运行阶段,会根据p中的isa指针找到对应的类,即Person类,再在Person类的缓存中查找eat方法的IMP(方法实现),如果没有找到,就会在Person类中的方法列表中找,如果找到会将该方法加入缓存并执行。

Runtime在Swift中的作用

Swift是静态语言,大多数函数方法的调用在编译阶段就确定了,但是,Swift和OC之间有很强的互操性,如果我们对方法和属性添加@objc标识时,我们可以通过Runtime API拿到,但是在我们的OC中是没办法进行调度的。

对于继承自NSObject类来说,如果我们想要动态的获取当前的属性和方法,必须在其声明前添加@objc关键字,方法交换需要添加 dynamic 标识,否则也是无法通过Runtime API获取的

1.反射

所谓反射就是可以动态获取类型、成员信息,在运⾏时可以调⽤⽅法、属性等⾏为的特性,Swift 是⼀⻔类型安全的语⾔,不⽀持我们像 OC 那样直接操作,它的标准库仍然提供了反射机制来让我们访问成员信息,Swift 的反射机制是基于⼀个叫 Mirror 的结构体来实现的。然后就可以通过它查询这个实例。

例如:

class Teacher {
    var age:Int = 18
    func teach() {
        print("teach")
    }
}
//⾸先通过构造⽅法构建⼀个Mirror实例,这⾥传⼊的参数是 Any,也就意味着当前可以是类,结构体,枚举等
let mirror = Mirror(reflecting: LGTeacher())
//接下来遍历 children 属性,这是⼀个集合
for child in mirror.children {
    //然后我们可以直接通过 label 输出当前的名称,value 输出当前反射的值
    print("\(child.label) : \(child.value)")
}

运行结果:

2.KVO

KVO也是通过Runtime来实现的,它允许对象在某些属性发送变化时通知其它对象,在观察的属性前面添加@objc dynamic进行启动。

3.方法交换

在OC中调用一个方法其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用OC的动态特性,可以实现在运行时偷换selector方法的实现,达到和方法挂钩的目的。

每一个类都有一个方法列表,存放在selector的名字和方法实现的映射关系,imp有点像函数指针,指向具体的方法实现。

可以利用method_exchanggeimplementations来交换两个方法的imp

可以利用class_replaceMethod来替换方法的imp

可以利用method_setimplementation来直接设置某个方法的imp

归根结底方法交换就是偷换了selector的imp

方法交换的用途:①防止数组越界 ②拓展系统类行为(如:在 UIViewController 中自动打印 viewDidLoad 的日志)


原文地址:https://blog.csdn.net/qq_74177011/article/details/144252732

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!