OC底层探索 - Runtime

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 方法查找的思维导图 runtime底层探索.png

在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)的查找流程如下:

  1. 通过obj的isa指针找到obj的class
  2. 在class的method list找到foo方法
  3. 如果class中没找到foo,继续往上找,superclass
  4. 一旦找到foo,执行它的实现IMP
  5. 查找不到,进入动态方法解析
  6. 解析不到,进入快速转发
  7. 快速转发,返回对象为nil,则进入完整的消息转发流程
  8. 实在找不到,抛出异常

这种效率太慢,每次都要查找,所以引入objc_cache,找到foo,把foo的method_name作为key,method_imp(函数指针)作为value存起来,结构是哈希表。

查找流程

image.png

猜你喜欢

转载自juejin.im/post/7074984603320221709