【OC梳理】runtime

什么是runtime

runtime是属于OC的底层,可以进行一些非常底层的操作(用OC是无法现实的, 不好实现)。 在我们平时编写的OC代码中, 程序运行过程时, 其实最终都是转成了runtime的C语言代码, runtime算是OC的幕后工作者。
例如:

// 调用无参数的方法:
[receiver selector];
// 运行时会被编译器转化为:
objc_msgSend(receiver, selector)

// 调用有参数的方法:
[receiver selector:(id)arg...];
// 运行时会被编译器转化为:
objc_msgSend(receiver, selector, arg, ...)

runtime的作用

  • 动态创建一个类(比如KVO的底层实现)
  • 动态地为某个类添加属性/方法。可以用于封装框架(扩展、修改) 这就是我们runtime机制的主要运用方向
  • 遍历一个类中所有的成员变量(属性)/所有方法。(比如字典–>模型:利用runtime遍历模型对象的所有属性, 根据属性名从字典中取出对应的值, 设置到模型的属性上;还有归档和解档,利用runtime遍历模型对象的所有属性)
  • 利用runtime实现使用Block回调的KVO
  • 利用runtime实现多播委托

runtime相关文件与函数

#import 
#import 

官方文档翻译有些地方不是很通顺,可以直接查看官方文档
Objective-C Runtime这里有runtime文件中方法的中文注释,不甚清晰,有空再自己整理一份。

runtime的使用

有三种方式可以使用Runtime:

  • Objective-C 源代码
  • NSObject 方法
  • Runtime 方法
Objective-C 源代码

通常情况下我们写的Objective-C代码,编译时会自动生成包含runtime特性的数据结构(包含了class、category、protocol中定义的信息)和方法。

NSObject类中定义的方法

Cocoa中大部分的类都是继承自NSObejct(NSProxy类例外,是一个抽象类)。
一些情况下,NSObject 类仅仅定义了完成某件事情的模板,并没有提供所需要的代码。例如 -description 方法,该方法返回类内容的字符串,该方法主要用来调试程序。NSObject 类并不知道子类的内容,所以它只是返回类的名字和对象的地址,NSObject 的子类可以重新实现。

还有一些 NSObject 的方法可以从 Runtime 系统中获取信息,允许对象进行自我检查。例如:

  • class方法返回对象的类;
  • isKindOfClass: 和 -isMemberOfClass: 方法检查对象是否存在于指定的类的继承体系中(是否是其子类或者父类或者当前类的成员变量);
  • respondsToSelector: 检查对象能否响应指定的消息;
  • conformsToProtocol:检查对象是否实现了指定协议类的方法;
  • methodForSelector: 返回指定方法实现的地址。
使用runtime库函数

常用的接口如下:

/// 获取类
Class PersonClass = object_getClass([Person class]);

/// SEL是selector在 Objc 中的表示:
SEL oriSEL = @selector(test1);

/// 获取类方法
Method oriMethod = Method class_getClassMethod(Class cls , SEL name);

/// 获取实例方法
Method class_getInstanceMethod(Class cls , SEL name)

/// 添加方法
BOOL addSucc = class_addMethod(xiaomingClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));

/// 替换原方法实现
class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));

/// 交换两个方法
method_exchangeImplementations(oriMethod, cusMethod);

/// 获取一个类的属性列表(返回值是一个数组)
objc_property_t *propertyList = class_copyPropertyList([self class], &count);

/// 获取一个类的方法列表(返回值是一个数组)
Method *methodList = class_copyMethodList([self class], &count);

/// 获取一个类的成员变量列表(返回值是一个数组)
Ivar *ivarList = class_copyIvarList([self class], &count);

/// 获取成员变量的名字
const char *ivar_getName(Ivar v);

/// 获取成员变量的类型
const char *ivar_getTypeEndcoding(Ivar v);

/// 获取一个类的协议列表(返回值是一个数组)
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);

/** 
  * set方法
  * 将值value 跟对象object 关联起来(将值value 存储到对象object 中)
  * 参数 object:给哪个对象设置属性
  * 参数 key:一个属性对应一个Key,将来可以通过key取出这个存储的值,key 可以是任何类型:double、int 等,建议用char 可以节省字节
  * 参数 value:给属性设置的值
  * 参数policy:存储策略 (assign 、copy 、 retain就是strong)
**/
void objc_setAssociatedObject(id object , const void *key ,id value ,objc_AssociationPolicy policy);

/// 利用参数key 将对象object中存储的对应值取出来
id objc_getAssociatedObject(id object , const void *key);

runtime相关术语

Method

Method 代表了可以独立完成一个功能的函数,其结构包括方法名方法类型 以及方法实现

typedef struct objc_method *Method;
struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}
SEL

SEL是方法选择器(Swift中是Selector类),其实就是runtime中发送message时带的方法名(怀疑是一个string,类似className + method的组合,这也是OC为什么不能进行函数重载的原因)。
OC中,performSelector系列方法所传的selector都是SEL的一个实例。

IMP

IMP是指向一个方法实现的函数指针,定义如下:

typedef id (*IMP)(id, SEL, ...); 
id

id 是一个参数类型,它是指向某个类的实例的指针。

Class

Class 其实是指向 objc_class 结构体的指针。定义如下:

typedef struct objc_class *Class;

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;

从 objc_class 可以看到,一个运行时类中关联了它的父类指针、类名、成员变量、方法、缓存以及附属的协议。

其中 objc_ivar_list 和 objc_method_list分别是成员变量列表和方法列表。

Ivar

Ivar 是表示成员变量的类型。包含了对象名称、对象类型,以及对象地址(基地址偏移字节)。

Cache

Cache 为方法调用的性能进行优化,每当实例对象接收到一个消息时,它不会直接在 isa 指针指向的类的方法列表中遍历查找能够响应的方法,因为每次都要查找效率太低了,而是优先在 Cache 中查找。

Runtime 系统会把被调用的方法存到 Cache 中,如果一个方法被调用,那么它有可能今后还会被调用,下次查找的时候就会效率更高。就像计算机组成原理中 CPU 绕过主存先访问 Cache 一样。


参考文章:
runtime从入门到精通(一)—— 初识runtime
runtime从入门到精通(二)—— 官方文档翻译
runtime从入门到精通(三)—— runtime常用接口方法
runtime从入门到精通(四)—— 一些runtime相关术语的数据结构

iOS Runtime 详解
iOS开发-Runtime详解
OC运行时Runtime探究

你可能感兴趣的:(【OC梳理】runtime)