Runtime原理探究

Runtime简介


运行时最主要的是消息机制

  • 对于C语言,函数调用在编译的时候会决定调用哪个函数
  • 对于OC函数,属于 动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用,
  • 在编译阶段,oc可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错
    在编译阶段,C语言调用未实现的函数就会报错
  • 如果向某个对象传递消息,在底层所有的方法都是普通的C语言函数,然而对象收到消息之后,究竟该调用哪个方法则完全取决于运行期决定,甚至可能在运行期改变,这些特性使得OC变成一门真正的动态语言
  • 在Runtime中,对象可以使用C语言中的结构体表示,而方法可以用C函数实现,另外在加上了额外的特性,这些结构体和函数被Runtime函数封装后,让OC的面向对象编程变为可能

Objective-C中的数据结构


1.id

运行时系统如何知道某个对象的类型呢?对象类型并不是在编译期就知道了,而是要在运行期查找,OC有个特殊类型id,它可以表示OC的任意对象类型,id类型定义在Runtime的头文件中:

struct obje_object {
          Class isa;
} *id;

由此可见,每个对象结构体的首个成员变量是Class类的isa,该变量定义了对象所属的类,通常指isa指针

objc_object

objc_object 是一个表示一个类实例的结构体,它的定义如下(objc/objc.h):

struct objc_object{
           Class isa OBJC_ISA_AVAILABILITY;
};
typedef struce objc_object *id;

可以看到,这个结构体只有一个实体,基指向其类的isa指针。这样,当我们向一个OC对象发送消息时,运行时库会根据实例对象的isa指针找到这个实例对象所属的类,Runtime库会在类的方法列表以及父类的方法列表中寻找与消息对应的selector指向的方法,找到后即运行这个方法。

2.Class

Class对象也定义在Runtime的头文件中,查看objc/runtime.h中的objc_class结构体:Objective-c中,类是由Class类型来表示的,它实际是一个指向objc_class结构体的指针。

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;// 父类
    const char * _Nonnull name                               OBJC2_UNAVAILABLE; // 类名
    long version                                             OBJC2_UNAVAILABLE;//类的版本信息,默认为0
    long info                                                OBJC2_UNAVAILABLE;//类信息,供运行期使用的一些位标识
    long instance_size                                       OBJC2_UNAVAILABLE;// 该类的实例变量大小
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;// 该类的成员变量链表
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;//方法定义的链表
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;//方法缓存
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;//协议链表
#endif

} OBJC2_UNAVAILABLE;

下面说下Class 结构体的几个主要变量:
1.isa:结构体的首个变量也是isa指针,这说明Class本身也是OC中的对象。isa指针非常重要,对象需要通过isa指针找到它的类,类需要isa找到元类,这在调用实例方法和类方法的时候起到重要作用。
2.super_class:结构体里还有个变量是super_class,它定义了本类的超类。类对象所属类型(isa指针所指向的类型)是另一个类,叫做元类。
3.ivars:成员变量列表,类的成员都在ivars里面。
4.methodLists:方法列表,类的实例方法都在methodLists里,类方法在元类的methodLists里面。methodLists是一个指针的指针,通过修改该指针指向指针的值,就可以动态的为某一个类添加成员方法。这就是Category实现的原理,同时也说明Category只可以为对象添加成员方法,不能添加成员变量。
5.cache:方法缓存列表,objc_msgSend(下文详解)每调用一次方法后,就会把该方法缓存到cache列表中,下次调用的时候,会优先从cache列表中寻找,如果cache没有,才从methodLists中查找方法,提高效率。

元类(Meta Class)

meta-class 是一个类对象的类。在上面我们提到,所有的类自身也是一个对象,我们可以向这个对象发送消息(即调用类方法)。既然是对象,那么它也是一个objc_object指针,它包含一个指向其类的一个isa指针,那么,这个isa指针指向什么呢?为了调用类方法,这个类的isa指针必须指向一个包含这个类方法的一个objc_class结构体。这就引出了meta-class的概念,meta-class中存储着一个类的所有类方法。所以,调用类方法的这个类对象的isa指针指向的就是meta-class 当我们向一个对象发送消息时,runtime会在这个对象所属的这个类的方法列表中查找方法;而向一个类发送消息时,会在这个类的meta-class的方法列表中查找。

再深入一下,meta-class 也是一个类,也可以向它发送一个消息,那么它的isa又是指向什么呢?为了不让这种结构无限延伸下去,Objective-C的设计者让所有的meta-class的isa指向基类的meta-class,以此作为他们的所属类。

即,任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己的所属类,而基类的meta-class的isa指针是指向它自己。

通过上面的描述,再加上对objc_class结构体中super_class指针的分析,我们就可以描绘出类及相应meta-class类的一个继承体系了,如下代码

image.png

上图 superclass指针代表继承关系,isa指针代表实例所属的类。类也是一个对象,它是另一个类的实例,这个就是”元类“,元类里面保存了类方法的列表,类里面保存了实例方法的列表。实例对象的isa指向类,类对象的isa指向元类,元类对象的isa指向一个根元类(root metaclass)。所有子类的元类都继承父类的元类,换而言之,类对象和元类对象有同样的继承关系。

Class是一个指向objc_class结构体的指针,而id是一个指向objc_object结构体的指针,其中的isa是一个指向objc_class结构体的指针。其中的id就是我们说的对象,Class就是我们所说的类。isa指针不总是指向实例对象所属的类,不能依靠它来确定类型,而是应该用isKindOfClass:方法来确定实例对象的类。因为KVO的实现机制就是将被观察对象的isa指针指向一个中间类而不是真实的类。

Category

Category是表示一个指向分类的结构体的指针,其定义如下:

/// An opaque type that represents a category.
typedef struct objc_category *Category;
struct objc_category {
    char * _Nonnull category_name                            OBJC2_UNAVAILABLE;
    char * _Nonnull class_name                               OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable instance_methods     OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable class_methods        OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
}  

这个结构体主要包含了分类定义的实例方法与类方法,其中instance_methods列表是objc_class中方法列表的一个子集,而class_methods列表是元类方法列表的一个子集。可发现,类别中没有ivar成员变量指针,也就意味着:类别中不能够添加实例变量和属性

struct objc_ivar_list *ivars             OBJC2_UNAVAILABLE;  // 该类的成员变量链表

3.SEL

  • 方法交换(method swizzing)
    在Objctive-C中,对象收到消息后,究竟会调用哪种方法需要在运行期才能解析出来。查找消息的唯一依据是选择子(selector),选择子(selector)与相应的方法(IMP)对应,利用Objective-C的动态特性,可以实现在运行时偷换选择子(selector)对应的方法实现,这就是方法交换 (method swizzing).
    每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向具体的Method实现。


    类的方法列表会把每个选择子都映射到相关的IMP之上

    image.png

我们可以新增选择子,也可以改变某个选择子所对应的IMP,还可以交换两个选择子所映射到的指针。

  • Objective-C中提供了三种API来动态替换类方法或实例方法的实现:
    1.class_replaceMethod替换类方法的定义。
class_replaceMethod(Class cls,SEL name,IMP imp,const char *types)

2.method_exchangeImplementations交换两个方法的实现。

method_exchangeImplementations(Method m1,Method m2)

3.method_setImplementation设置一个方法的实现

method_setImplementation(Method m,IMP imp)

先说这三个方法的区别:

  • class_replaceMethod:当类中没有想替换的原方法时,该方法调用class_addMethod来为类增加一个新方法,也正因如此,class_replaceMethod在调用时需传入types参数,而其余两个缺不需要。
  • method_exchangeImplementations: 内部实现就是调用了两次method_setImplemetation方法。

再来看看他们的使用场景:

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        SEL originalSelector = @selector(willMoveToSuperview:);
        SEL swizzledSelector = @selector(myWillMoveToSuperview:);

        Method originalMethod = class_getInstanceMethod(self, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
        
        BOOL didAddMethod = class_addMethod(self, 
                                            originalSelector,
                                            method_getImplementation(swizzledMethod),
                                            method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(self, 
                                swizzledSelector, 
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

- (void)myWillMoveToSuperview:(UIView *)newSuperview
{
    NSLog(@"WillMoveToSuperview: %@", self); 
    [self myWillMoveToSuperview:newSuperview];
}

总结

class_replaceMethod,当需要替换的方法有可能不存在是,可以考虑使用该方法。
method_exchangeImplementations,当需要交换两个方法时使用
method_setImplementation是最简单的用法,当仅仅需要为一个方法设置其实现方式时实现。

4.Ivar

ivar 代表类中实例变量的类型,在Runtime的头文件中的定义如下:
typedef struct objc_ivar *Ivar;
objc_ivar的定义如下:

struct objc_ivar {
    char * _Nullable ivar_name                               OBJC2_UNAVAILABLE;
    char * _Nullable ivar_type                               OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}

class_copyIvarList(Class cls,unsigned int *outCount)可以使用这个方法获取某个类的成员变量列表

5.objc_property_t

objc_property_t是属性,在Runtime的头文件中的定义如下:
typedef struct objc_property *objc_property_t;
class_copyPropertyList(Class cls, unsigned int *outCount) 可以使用这个方法获取某个类的属性列表。

Runtime原理探究

你可能感兴趣的:(Runtime原理探究)