Runtime的原理及其应用

171 阅读7分钟

本文目录

  • Runtime是什么
  • Runtime的原理
  • Runtime的应用

Runtime是什么

Objective-C 是一个动态语言,这意味着它不仅需要一个编译器,也需要一个运行时系统来动态得创建类和对象、进行消息传递和转发。这个系统和机制称之为Runtime。

Runtime又叫运行时,是一套底层的C语言API,是iOS的系统的核心之一。开发者在编码过程中,可以给任一个对象发送消息,在编译阶段只是确定了要向接受这发送这条消息,而接受者将要如何响应和处理这条消息,那就要看运行时来决定了。

OC的函数,属于动态调用过程,在编译期并不能决定真正调用哪个函数,只有在真正运行时才会根据函数的名称找到对应的函数来调用

Runtime的原理

Runtime的数据结构和源码解析

23_16.png

  • 一个类当中有哪些属性呢,以Objc1.0为例,从上到下分别是,
  • isa指针(isa),
  • 父类指针(super_class),
  • 名称(name),
  • 版本(version),
  • 信息(info),
  • (instance_size),
  • 成员变量列表的指针(ivars),
  • 指向objc_method_list指针的指针(methodlists),
  • 缓存方法列表的指针(cache),
  • 遵循的协议列表的指针(protocols)

objc1.0的代码


struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
    
#if !__OBJC2__
    Class super_class               OBJC2_UNAVAILABLE;
    const char *name                OBJC2_UNAVAILABLE;
    long version                    OBJC2_UNAVAILABLE;
    long info                       OBJC2_UNAVAILABLE;
    long instance_size              OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars    OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists       OBJC2_UNAVAILABLE;
    struct objc_cache *cache                    OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols        OBJC2_UNAVAILABLE;
#endif
    
} OBJC2_UNAVAILABLE;

objc2.0的示意图,代码太长就不贴了,想看的可以去底部的第一个参考链接里看

runtime_sourceCode.png

当一个对象的实例方法被调用的时候,会通过isa找到相应的类,然后在该类的class_data_bits_t中去查找方法。class_data_bits_t是指向了类对象的数据区域。在该数据区域内查找相应方法的对应实现。

但是在我们调用类方法的时候,类对象的isa里面是什么呢?这里为了和对象查找方法的机制一致,遂引入了元类(meta-class)的概念。

23_7.png

对象的实例方法调用时,通过对象的 isa 在类中获取方法的实现。

类对象的类方法调用时,通过类的 isa 在元类中获取方法的实现。 图中实线是 super_class指针,虚线是isa指针。

  1. Root class (class)其实就是NSObject,NSObject是没有超类的,所以Root class(class)的superclass指向nil。
  2. 每个Class都有一个isa指针指向唯一的Meta class
  3. Root class(meta)的superclass指向Root class(class),也就是NSObject,形成一个回路。
  4. 每个Meta class的isa指针都指向Root class (meta)。

我们其实应该明白,类对象和元类对象是唯一的,对象是可以在运行时创建无数个的。而在main方法执行之前,从 dyld到runtime这期间,类对象和元类对象在这期间被创建。具体可看sunnyxx这篇iOS 程序 main 函数之前发生了什么

Runtime的运行过程

  1. 消息发送阶段—objc_msgSend源码解析
  2. 消息转发阶段-Message Forwarding阶段

runtime_messageSend.png

id objc_msgSend ( id self, SEL op, ... );
typedef struct objc_selector *SEL;

objc_selector是一个映射到方法的C字符串。需要注意的是@selector()选择子只与函数名有关。不同类中相同名字的方法所对应的方法选择器是相同的,即使方法名字相同而变量类型不同也会导致它们具有相同的方法选择器。由于这点特性,也导致了OC不支持函数重载。

在receiver拿到对应的selector之后,如果自己无法执行这个方法,那么该条消息要被转发。或者临时动态的添加方法实现。如果转发到最后依旧没法处理,程序就会崩溃。

发送消息具体过程

  1. 检测这个 selector是不是要忽略的。
  2. 检查target是不是为nil。如果这里有相应的nil的处理函数,就跳转到相应的函数中。如果没有处理nil的函数,就自动清理现场并返回。这一点就是为何在OC中给nil发送消息不会崩溃的原因。
  3. 确定不是给nil发消息之后,在该class的缓存中查找方法对应的IMP实现。如果找到,就跳转进去执行。如果没有找到,就在方法分发表里面继续查找,一直找到NSObject为止。
  4. 如果还没有找到,那就需要开始消息转发阶段了。至此,发送消息Messaging阶段完成。这一阶段主要完成的是通过select()快速查找IMP的过程。

当前的SEL无法找到相应的IMP的时候,开发者可以通过重写- (id)forwardingTargetForSelector:(SEL)aSelector方法来“偷梁换柱”,把消息的接受者换成一个可以处理该消息的对象。

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if(aSelector == @selector(Method:)){
        return otherObject;
    }
    return [super forwardingTargetForSelector:aSelector];
}

Runtime的应用

Objc 在三种层面上与 Runtime 系统进行交互

  1. 通过 Objective-C 源代码 一般情况开发者只需要编写 OC 代码即可,Runtime 系统自动在幕后把我们写的源代码在编译阶段转换成运行时代码,在运行时确定对应的数据结构和调用具体哪个方法。

  2. 通过 Foundation 框架的 NSObject 类定义的方法 在OC的世界中,除了NSProxy类以外,所有的类都是NSObject的子类。在Foundation框架下,NSObject和NSProxy两个基类,定义了类层次结构中该类下方所有类的公共接口和行为。NSProxy是专门用于实现代理对象的类,这个类暂时本篇文章不提。这两个类都遵循了NSObject协议。在NSObject协议中,声明了所有OC对象的公共方法。

    在NSObject协议中,有以下5个方法,是可以从Runtime中获取信息,让对象进行自我检查。


- (Class)class OBJC_SWIFT_UNAVAILABLE("use 'anObject.dynamicType' instead");
- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
- (BOOL)respondsToSelector:(SEL)aSelector;

  1. 通过对 Runtime 库函数的直接调用 关于库函数可以在 #import <objc/runtime.h> (xcode)中查看 Runtime 函数的详细文档。

    我们可以通过类名/方法名反射得到相应的类和方法,也可以替换某个类的方法为新的实现,理论上你可以在运行时通过类名/方法名调用到任何 OC 方法,替换任何类的实现以及新增任意类。 runtime常用的方法

//获取cls类对象所有成员ivar结构体
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
//获取cls类对象name对应的实例方法结构体
Method class_getInstanceMethod(Class cls, SEL name)
//获取cls类对象name对应类方法结构体
Method class_getClassMethod(Class cls, SEL name)
//获取cls类对象name对应方法imp实现
IMP class_getMethodImplementation(Class cls, SEL name)
//测试cls对应的实例是否响应sel对应的方法
BOOL class_respondsToSelector(Class cls, SEL sel)
//获取cls对应方法列表
Method *class_copyMethodList(Class cls, unsigned int *outCount)
//测试cls是否遵守protocol协议
BOOL class_conformsToProtocol(Class cls, Protocol *protocol)
//为cls类对象添加新方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
//替换cls类对象中name对应方法的实现
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
//为cls添加新成员
BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *types)
//为cls添加新属性
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
//获取m对应的选择器
SEL method_getName(Method m)
//获取m对应的方法实现的imp指针
IMP method_getImplementation(Method m)
//获取m方法的对应编码
const char *method_getTypeEncoding(Method m)
//获取m方法参数的个数
unsigned int method_getNumberOfArguments(Method m)
//copy方法返回值类型
char *method_copyReturnType(Method m)
//获取m方法index索引参数的类型
char *method_copyArgumentType(Method m, unsigned int index)
//获取m方法返回值类型
void method_getReturnType(Method m, char *dst, size_t dst_len)
//获取方法的参数类型
void method_getArgumentType(Method m, unsigned int index, char *dst, size_t dst_len)
//设置m方法的具体实现指针
IMP method_setImplementation(Method m, IMP imp)
//交换m1,m2方法对应具体实现的函数指针
void method_exchangeImplementations(Method m1, Method m2)
//获取v的名称
const char *ivar_getName(Ivar v)
//获取v的类型编码
const char *ivar_getTypeEncoding(Ivar v)
//设置object对象关联的对象
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
//获取object关联的对象
id objc_getAssociatedObject(id object, const void *key)
//移除object关联的对象
void objc_removeAssociatedObjects(id object)

这3种方式具体可以在项目组做什么呢?以下就是,供你参考

  1. 实现多继承Multiple Inheritance
  2. Method Swizzling
  3. Aspect Oriented Programming
  4. Isa Swizzling
  5. Associated Object关联对象
  6. 动态的增加方法
  7. NSCoding的自动归档和自动解档
  8. 字典和模型互相转换

具体参考文档:如何正确使用 Runtime

Runtime相关面试题整理

Runtime经典面试题

参考文档

isa 和 Class

消息发送与转发

如何正确使用 Runtime