RunTime 运行时详解
最近在做一些知识点的总结。写到了runtime,引用了两位作者的文章,自己也做了一些归纳。
iOS 开发:『Runtime』详解(一)基础知识
iOS Runtime详解
Runtime介绍
我们都知道,将源代码转换为可执行的程序,通常要经过三个步骤:编译、链接、运行。不同的编译语言,在这三个步骤中所进行的操作又有些不同。
C 语言 作为一门静态类语言,在编译阶段就已经确定了所有变量的数据类型,同时也确定好了要调用的函数,以及函数的实现。
而 Objective-C 语言 是一门动态语言。在编译阶段并不知道变量的具体数据类型,也不知道所真正调用的哪个函数。只有在运行时间才检查变量的数据类型,同时在运行时才会根据函数名查找要调用的具体函数。这样在程序没运行的时候,我们并不知道调用一个方法具体会发生什么。
Ojective-C 语言 把一些决定性的工作从编译阶段、链接阶段推迟到 运行时阶段 的机制,使得 Objective-C 变得更加灵活。我们甚至可以在程序运行的时候,动态的去修改一个方法的实现,这也为大为流行的『热更新』提供了可能性。
而实现 Objective-C 语言 运行时机制 的一切基础就是 Runtime。
Runtime 实际上是一个库,这个库使我们可以在程序运行时动态的创建对象、检查对象,修改类和对象的方法。
下面是runtime的官方介绍
Objective-C 扩展了 C 语言,并加入了面向对象特性和 Smalltalk 式的消息传递机制。而这个扩展的核心是一个用 C 和 编译语言 写的 Runtime 库。它是 Objective-C 面向对象和动态机制的基石。
Objective-C 是一个动态语言,这意味着它不仅需要一个编译器,也需要一个运行时系统来动态得创建类和对象、进行消息传递和转发。理解 Objective-C 的 Runtime 机制可以帮我们更好的了解这个语言,适当的时候还能对语言进行扩展,从系统层面解决项目中的一些设计或技术问题。了解 Runtime ,要先了解它的核心 - 消息传递 (Messaging)。
消息机制的基本原理
Objective-C 语言 中,对象方法调用都是类似 [receiver selector]; 的形式,其本质就是让对象在运行时发送消息的过程。
我们来看看方法调用 [receiver selector]; 在『编译阶段』和『运行阶段』分别做了什么?
编译阶段:[receiver selector]; 方法被编译器转换为:
objc_msgSend(receiver,selector) (不带参数)
objc_msgSend(recevier,selector,org1,org2,…)(带参数)
运行时阶段:消息接受者 recevier 寻找对应的 selector。
通过 recevier 的 isa 指针 找到 recevier 的 Class(类);
在 Class(类) 的 cache(方法缓存) 的散列表中寻找对应的 IMP(方法实现);
如果在 cache(方法缓存) 中没有找到对应的 IMP(方法实现) 的话,就继续在 Class(类) 的 method list(方法列表) 中找对应的 selector,如果找到,填充到 cache(方法缓存) 中,并返回 selector;
如果在 Class(类) 中没有找到这个 selector,就继续在它的 superClass(父类)中寻找;
一旦找到对应的 selector,直接执行 recevier 对应 selector 方法实现的 IMP(方法实现)。
若找不到对应的 selector,消息被转发或者临时向 recevier 添加这个 selector 对应的实现方法,否则就会发生崩溃。
在上述过程中涉及了好几个新的概念:objc_msgSend、isa 指针、Class(类)、IMP(方法实现) 等,下面我们来具体讲解一下各个概念的含义。
Runtime 中的概念解析
Class(类)
在 objc/runtime.h 中,Class(类) 被定义为指向 objc_class 结构体 的指针,objc_class 结构体 的数据结构如下
/// An opaque type that represents an Objective-C class.
typedef struct objc_class*Class;
struct objc_class {
Class _Nonnull isa; // objc_class 结构体的实例指针
if !__OBJC2__
Class _Nullable super_class; // 指向父类的指针
const char * _Nonnull name; // 类的名字
long version; // 类的版本信息,默认为 0
long info; // 类的信息,供运行期使用的一些位标识
long instance_size; // 该类的实例变量大小;
struct objc_ivar_list * _Nullable ivars; // 该类的实例变量列表
struct objc_method_list * _Nullable * _Nullable methodLists; // 方法定义的列表
struct objc_cache * _Nonnull cache; // 方法缓存
struct objc_protocol_list * _Nullable protocols; // 遵守的协议列表
endif
};
从中可以看出,objc_class 结构体 定义了很多变量:自身的所有实例变量(ivars)、所有方法定义(methodLists)、遵守的协议列表(protocols)等。objc_class 结构体 存放的数据称为 元数据(metadata)。
objc_class 结构体 的第一个成员变量是 isa 指针,isa 指针 保存的是所属类的结构体的实例的指针,这里保存的就是 objc_class 结构体的实例指针,而实例换个名字就是 对象。换句话说,Class(类) 的本质其实就是一个对象,我们称之为 类对象。
// 添加方法
BOOL class_addMethod(Class cls,SEL name,IMP imp,constchar*types);
// 获取实例方法
Method class_getInstanceMethod(Class cls,SEL name);
// 获取类方法
Method class_getClassMethod(Class cls,SEL name);
// 获取所有方法的数组
Method*class_copyMethodList(Class cls,unsignedint*outCount);
// 替代方法的实现
IMP class_replaceMethod(Class cls,SEL name,IMP imp,constchar*types);
// 返回方法的具体实现
IMP class_getMethodImplementation(Class cls,SEL name);
IMP class_getMethodImplementation_stret(Class cls,SEL name);
// 类实例是否响应指定的
selectorBOOL class_respondsToSelector(Class cls,SEL sel);
Class class = [self class];
///类实例的大小
size_t value = class_getInstanceSize(class);
///类的版本号
int version = class_getVersion(class);
///类名
const char * name = class_getName(class); //? *的意义
///父类
Class superClass = class_getSuperclass(class);
/// 该类的成员变量列表
unsigned int count = 0;
Ivar * ivars = class_copyIvarList(class, &count);
for (int i = 0; i < count; i++) {
Ivar ivar = ivars[i];
NSString * ivarName = [[NSString alloc]initWithUTF8String:ivar_getName(ivar)];
NSLog(@"成员变量 = %@",ivarName);
}
///类的方法列表
unsigned int methodCount = 0;
Method * methods = class_copyMethodList(class, &methodCount);
for (int i = 0 ; i < methodCount; i++) {
Method method = methods[i];
NSString * methodName = NSStringFromSelector(method_getName(method));
NSLog(@"方法名称 = %@",methodName);
}
///类的属性列表
unsigned int propertyCount = 0;
objc_property_t * propertys = class_copyPropertyList(class, &propertyCount);
for (int i = 0; i < propertyCount; i ++ ) {
objc_property_t property = propertys[i];
NSString * propertyName = [[NSString alloc]initWithUTF8String:property_getName(property)];
NSLog(@"属性列表 = %@",propertyName);
}
///类的协议列表
unsigned int protocolCount = 0;
Protocol * __unsafe_unretained *list = class_copyProtocolList(class, &protocolCount);
for (int i = 0; i < protocolCount; i++) {
Protocol * protocol = list[i];
NSLog(@"协议列表 = %@",NSStringFromProtocol(protocol));
}
NSLog(@"%zu 版本 = %d,名称 = %s,父类= %@",value,version,name,NSStringFromClass(superClass));
这里有些疑问,属性列表不是成员变量的子集,成员变量也不是属性列表的子集。
属性列表包含协议中存在的属性,但是成员变量并不包括。
成员变量包括非属性的成员变量。所以两者有交集但是并不相互包括。
方法交换
-(void)exchangeMethod{
Person * person = [Person new];
Method method1 = class_getInstanceMethod([Person class], @selector(functionOne));
Method method2 = class_getInstanceMethod([Person class], @selector(functionTwo));
[person functionOne];
[person functionTwo];
method_exchangeImplementations(method1, method2);
[person functionOne];
[person functionTwo];
}

可以看到方法打印,已经进行了交换。
运行时消息转发
可参考我写的这篇文章 iOS运行时消息转发