各位iOS搬砖工,相信大家在面试的时候经常会遇到Runtime相关的知识点,但是由于日常开发中的确很少用到,所以大家的回答也不尽人意,今天就由我带大家探索一番Runtime;
Runtime是什么:
我们都知道,将源代码转换为可执行的程序,通常要经过三个步骤:编译、链接、运行。不同的编译语言,在这三个步骤中所进行的操作又有些不同;
C语言作为一门静态类语言,在它编译的阶段就已经确定了所有变量的数据类型,同时也确定好了要调用的函数,以及函数的实现;
而Objective-C语言是一门动态语言。在编译阶段并不知道变量的具体数据类型,也不知道所真正调用的哪个函数。只有在运行时间才检查变量的数据类型,同时在运行时才会根据函数名查找要调用的具体函数。这样在程序没运行的时候,我们并不知道调用一个方法具体会发生什么。
Objective-C语言把一些决定性的工作从编译阶段、链接阶段推迟到了运行时阶段的机制,使得Objective-C变得更加灵活。我们甚至可以在程序运行的时候,动态的去修改一个方法的实现。而实现Objective-C语言运行时机制的一切基础就是Runtime。
Runtime其实有两个版本:modern和legacy。我们现在用的Object2.0采用的是现行(modern)版的Runtime系统,只能运行在iOS和macOS10.5之后的64位程序中。而macOS较老的32位程序扔采用Object 1中的(早期)legacy版本的Runtime系统。这两个版本最大的区别在于当你更改一个类的实例变量的布局时,在早期版本中你需要重新编译他的子类,而现行版本就不需要。
Runtime基本是用C和汇编写的,可见苹果为了动态系统的高效而作出的努力。苹果和GNU(一个自由的操作系统)各自维护一个开源的runtime版本,这两个版本之间都在努力的保持一致。
高级编程语言想要成为可执行文件需要先编译为汇编语言再汇编为机器语言,机器语言也是计算机能够识别的唯一语言,但是OC并不能直接编译为汇编语言,而是要先转写为纯C语言再进行编译和汇编的操作,从OC到C的过渡就是由runtime来实现的。然后我们使用OC进行面向对象开发,而C语言更多的是面向过程开发,这就需要将面向对象的类转变为面向过程的结构体。
Runtime实际上是一个库,这个库使我们可以在程序运行时动态的创建对象、检查对象、修改类和对象的方法。
了解Runtime,要先了解他们核心-消息传递(Messaging)
Runtime消息传递(Messaging):
Objective-C语言中,对象方法调用都是类似[receiver selector];的形式,其本质就是让对象在运行时发送消息的过程。
[receiver selector]
在编译阶段和运行阶段分别作了什么:
1.编译阶段:[ receiver selector] 方法被编译器转换为:
-objc_msgSend(receiver,selector)(不带参数)
-objc_msgSend(receiver,selector,org1,org2,…)(带参数)
2.运行时阶段:消息接受者receiver寻找对应的selector。
-通过receiver的isa指针找到receiver的 Class(类);
-在Class(类)的cache(方法缓存)的散列表中寻找对应的IMP(方法实现);
-如果在cache中没有找到对应的IMP的话,就继续在Class中的method list(方法列表)中找对应的selector,如果找到,填充到cache中,并且返回selector
-如果在Class类中没有找到这个selector,就继续在它的superClass中寻找;
-一旦找到对应的selector,直接执行receiver对应selector方法实现的IMP。
-若找不到对应的selector,消息被转发或者临时向receiver添加这个selector对应的实现方法,否则就会发生崩溃。
相关应用:黑魔法:方法添加和替换
添加方法:
//class_addMethod(Class _Nullable __unsafe_unretained cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)
class_addMethod([self class],sel,(IMP)fooMethod,”v@:”);
替换方法:
@implementation ViewController
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewDidLoad);
SEL swizzledSelector = @selector(jkviewDidLoad);
Method originalMethod = class_getInstanceMethod(class,originalSelector);
Method swizzledMethod = class_getInstanceMethod(class,swizzledSelector);
//judge the method named swizzledMethod is already existed.
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
// if swizzledMethod is already existed.
if (didAddMethod) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}
else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)jkviewDidLoad {
NSLog(@"替换的方法");
[self jkviewDidLoad];
}
- (void)viewDidLoad {
NSLog(@"自带的方法");
[super viewDidLoad];
}
@end
swizzling应该只在+load中完成。在Objective-C的运行时中,每个类有两个方法都会自动调用。+load是在一个类被初始装载时调用,+initialize 是在应用第一次调用该类的类方法或实例方法前调用的。两个方法都是可选的,并且只有在方法被实现的情况下才会被调用。
Swizzling应该只在dispatch_once中完成,由于swizzling改变了全局的状态,所以我们需要确保没个预防措施在运行时都是可用的。原子操作就是这样一个用于确保代码只会被执行一次的预防措施,就算是在不同的线程中也能确保代码只执行一次。Grand Central Dispatch的dispatch_once满足了所需要的需求,并且应该被当做使用swizzling的初始化单例方法的标准;
文章收录自行走少年郎的
iOS 开发:『Runtime』详解(一)基础知识
以及jackyshan的
iOS Runtime详解