一,RunTime是什么,究竟什么时候用
1.第一个问题,
1》runtime实现的机制是什么,怎么用,一般用于干嘛?
runtime是一套比较底层的纯C语言API, 属于1个C语言库, 包含了很多底层的C语言API。
话不多说 LIST:
OC :
[[MJPerson alloc] init] 的RunTimeobjc_msgSend(objc_msgSend(“MJPerson” , “alloc”), “init”)
2.第二个问题
runtime 用来干什么呢??用在那些地方呢?怎么用呢?
runtime是属于OC的底层, 可以进行一些非常底层的操作(用OC是无法现实的, 不好实现)
-
在程序运行过程中, 动态创建一个类(比如KVO的底层实现)
-
在程序运行过程中, 动态地为某个类添加属性\方法, 修改属性值\方法
-
遍历一个类的所有成员变量(属性)\所有方法
二,RunTime的基本语法
1.NSObject的方法
Cocoa 中大多数类都继承于NSObject
类,也就自然继承了它的方法。最特殊的例外是NSProxy
,它是个抽象超类,它实现了一些消息转发有关的方法,可以通过继承它来实现一个其他类的替身类或是虚拟出一个不存在的类,说白了就是领导把自己展现给大家风光无限,但是把活儿都交给幕后小弟去干。
有的NSObject
中的方法起到了抽象接口的作用,比如description
方法需要你重载它并为你定义的类提供描述内容。NSObject
还有些方法能在运行时获得类的信息,并检查一些特性,比如class
返回对象的类;isKindOfClass:
和isMemberOfClass:
则检查对象是否在指定的类继承体系中;respondsToSelector:
检查对象能否响应指定的消息;conformsToProtocol:
检查对象是否实现了指定协议类的方法;methodForSelector:
则返回指定方法实现的地址。
Runtime术语
objc_msgSend:
它的真身是这样的:
id objc_msgSend ( id self, SEL op, ... );
下面将会逐渐展开介绍一些术语,其实它们都对应着数据结构。
SEL:选择子
objc_msgSend
函数第二个参数类型为SEL
,它是selector
在Objc中的表示类型(Swift中是Selector类)。selector
是方法选择器,可以理解为区分方法的 ID,而这个 ID 的数据结构是SEL
:
typedef struct objc_selector *SEL;
不同类中相同名字的方法所对应的方法选择器是相同的,即使方法名字相同而变量类型不同也会导致它们具有相同的方法选择器,于是 Objc 中方法命名有时会带上参数类型(
NSNumber
一堆抽象工厂方法拿走不谢),Cocoa 中有好多长长的方法哦。
id
objc_msgSend
第一个参数类型为id
,大家对它都不陌生,它是一个指向类实例的指针:
typedef struct objc_object *id;
那objc_object
又是啥呢:
struct objc_object { Class isa; };
objc_object
结构体包含一个isa
指针,根据isa
指针就可以顺藤摸瓜找到对象所属的类。
Class
之所以说isa
是指针是因为Class
其实是一个指向objc_class
结构体的指针:
typedef struct objc_class *Class;
而objc_class
就是我们摸到的那个瓜,里面的东西多着呢:具体不在赘述,对我们理解这个无关紧要
最后要提到的还有一个objc_cache
,顾名思义它是缓存,它在objc_class
的作用很重要,在后面会讲到。
一个 ObjC 类同时也是一个对象,为了处理类和对象的关系,runtime 库创建了一种叫做元类 (Meta Class) 的东西。当你发出一个类似[NSObject alloc]
的消息时,你事实上是把这个消息发给了一个类对象 (Class Object) ,这个类对象必须是一个元类的实例,而这个元类同时也是一个根元类 (root meta class) 的实例。你会说 NSObject
的子类时,你的类就会指向 NSObject
做为其超类。但是所有的元类最终都指向根元类为其超类。所有的元类的方法列表都有能够响应消息的类方法。所以当 [NSObject alloc]
这条消息发给类对象的时候,objc_msgSend()
会去它的元类里面去查找能够响应消息的方法,如果找到了,然后对这个类对象执行方法调用。
Method
Method
是一种代表类中的某个方法的类型。
typedef struct objc_method *Method;
而objc_method
在上面的方法列表中提到过,它存储了方法名,方法类型和方法实现:
struct objc_method {SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
-
方法名类型为
SEL
,前面提到过相同名字的方法即使在不同类中定义,它们的方法选择器也相同。 -
方法类型
method_types
是个char
指针,其实存储着方法的参数类型和返回值类型。 -
method_imp
指向了方法的实现,本质上是一个函数指针
Ivar
Ivar
是一种代表类中实例变量的类型。
typedef struct objc_ivar *Ivar;
而objc_ivar
在上面的成员变量列表中也提到过:
struct objc_ivar {char *ivar_name OBJC2_UNAVAILABLE;
char *ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
IMP
IMP
在objc.h
中的定义是:
typedef id (*IMP)(id, SEL, ...);
它就是一个函数指针,这是由编译器生成的。当你发起一个 ObjC 消息之后,最终它会执行的那段代码,就是由这个函数指针指定的。而IMP这个函数指针就指向了这个方法的实现。既然得到了执行某个实例某个方法的入口,我们就可以绕开消息传递阶段,直接执行方法,这在后面会提到。
你会发现IMP
指向的方法与objc_msgSend
函数类型相同,参数都包含id
和SEL
类型。每个方法名都对应一个SEL
类型的方法选择器,而每个实例对象中的SEL
对应的方法实现肯定是唯一的,通过一组id
和SEL
参数就能确定唯一的方法实现地址;反之亦然。
Cache
在runtime.h
中Cache的定义如下:
typedef struct objc_cache *Cache
还记得之前objc_class
结构体中有一个struct objc_cache *cache
吧,它到底是缓存啥的呢,先看看objc_cache
的实现:
struct objc_cache {unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};
Cache
为方法调用的性能进行优化,通俗地讲,每当实例对象接收到一个消息时,它不会直接在isa
指向的类的方法列表中遍历查找能够响应消息的方法,因为这样效率太低了,而是优先在Cache
中查找。Runtime 系统会把被调用的方法存到Cache
中(理论上讲一个方法如果被调用,那么它有可能今后还会被调用),下次查找的时候效率更高。这根计算机组成原理中学过的 CPU 绕过主存先访问Cache
的道理挺像,而我猜苹果为提高Cache
命中率应该也做了努力吧。
消息
前面做了这么多铺垫,现在终于说到了消息了。Objc 中发送消息是用中括号([]
)把接收者和消息括起来,而直到运行时才会把消息与方法实现绑定。