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原理探究

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342