Runtime是什么
因为Objc是一门动态语言,所以它总是想办法把一些决定工作从编译连接推迟到运行时。也就是说只有编译器是不够的,还需要一个运行时系统 (runtime system) 来执行编译后的代码。这就是 Objective-C Runtime 系统存在的意义,它是整个Objc运行框架的一块基石。
Runtime是由c、c++、汇编提供的一整套运行时功能。
Runtime相关头文件内容
ios的sdk中 usr/include/objc文件夹下面的文件
List.h
NSObjCRuntime.h
NSObject.h
Object.h
Protocol.h
a.txt
hashtable.h
hashtable2.h
message.h
module.map
objc-api.h
objc-auto.h
objc-class.h
objc-exception.h
objc-load.h
objc-runtime.h
objc-sync.h
objc.h
runtime.h
复制代码
都是和运行时相关的头文件,其中主要使用的函数定义在message.h和runtime.h这两个文件中。 在message.h中主要包含了一些向对象发送消息的函数,这是OC对象方法调用的底层实现。 runtime.h是运行时最重要的文件,其中包含了对运行时进行操作的方法。 主要包括
//runtime.h
// An opaque type that represents a method in a class definition. 一个类型,代表着类定义中的一个方法
typedef struct objc_method *Method;
/// An opaque type that represents an instance variable.代表实例(对象)的变量
typedef struct objc_ivar *Ivar;
/// An opaque type that represents a category.代表一个分类
typedef struct objc_category *Category;
/// An opaque type that represents an Objective-C declared property.代表OC声明的属性
typedef struct objc_property *objc_property_t;
// Class代表一个类,它在objc.h中这样定义的 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;
复制代码
这些类型的定义,对一个类进行了完全的分解,将类定义或者对象的每一个部分都抽象为一个类型type,对操作一个类属性和方法非常方便。OBJC2_UNAVAILABLE
标记的属性是Ojective-C 2.0不支持的,但实际上可以用响应的函数获取这些属性,例如:如果想要获取Class的name属性,可以按如下方法获取:
Class classPerson = Person.class;
// printf("%s\n", classPerson->name); //用这种方法已经不能获取name了 因为OBJC2_UNAVAILABLE
const char *cname = class_getName(classPerson);
printf("%s", cname); // 输出:Person
复制代码
2.1 函数的定义
对对象进行操作的方法一般以object_
开头
对类进行操作的方法一般以class_
开头
对类或对象的方法进行操作的方法一般以method_
开头
对成员变量进行操作的方法一般以ivar_
开头
对属性进行操作的方法一般以property_
开头
对协议进行操作的方法一般以protocol_
开头
根据以上的函数的前缀 可以大致了解到层级关系。对于以objc_
开头的方法,则是runtime最终的管家,可以获取内存中类的加载信息,类的列表,关联对象和关联属性等操作。
例如:使用runtime对当前的应用中加载的类进行打印,别被吓一跳。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
unsigned int count = 0;
Class *classes = objc_copyClassList(&count);
for (int i = 0; i < count; i++) {
const char *cname = class_getName(classes[i]);
printf("%s\n", cname);
}
}
复制代码
方法
首先看一下Runtime 方法查找的思维导图
在OC中,方法的本质就是消息发送,objc_msgSend
,通过汇编代码和通过Clang编译成c++代码,我们可以看到在oc中的方法最终会转换
//参数为:接收器、选择子、多参数
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
复制代码
其中接收器指消息的接收方,也就是对象,选择子也就是该对象的SEL。
SEL和IMP
SEL:类成员的方法指针,不同于函数指针,只是个方法编号 IMP:函数指针,指向我们定义的函数 如果使用书本来类比,那么SEL就是书本的目录,IMP就是书本的页码,方法函数就是该页码下的内容。
方法查找
举个例子 [obj foo] -> objc_msg(obj, foo)的查找流程如下:
- 通过obj的isa指针找到obj的class
- 在class的method list找到foo方法
- 如果class中没找到foo,继续往上找,superclass
- 一旦找到foo,执行它的实现IMP
- 查找不到,进入动态方法解析
- 解析不到,进入快速转发
- 快速转发,返回对象为nil,则进入完整的消息转发流程
- 实在找不到,抛出异常
这种效率太慢,每次都要查找,所以引入objc_cache,找到foo,把foo的method_name作为key,method_imp(函数指针)作为value存起来,结构是哈希表。
查找流程