iOS RunTime 详解

本文讲述 iOS Runtime 相关的知识点,从下面几个方面探寻 iOS Runtime的实现机制。

  • Runtime 介绍
  • Runtime 概念解析
  • Runtime 消息机制
  • Category 底层原理

Runtime 介绍

Objective C 是非常实用实用的语言,是在 C 语言基础上增加了面向对象编程语言的特性和 Smalltalk 的消息机制,完全兼容 C 语言。

将代码转换为可执行的程序,通常需要三个步骤:编译、链接、运行,不同的语言,这些步骤中的操作会有些不同。

Objective C 是一种动态语言,这意味着有些编译和链接阶段的操作,在程序运行时才执行,所以要有一个运行时的机制,来动态的创建类和对象,调用方法,这个机制就是 iOS Runtime 机制。Objective C 在编译阶段并不知道变量的具体数据类型,也不知道所真正调用的哪个函数。只有在运行时间才检查变量的数据类型,同时在运行时才会根据函数名查找要调用的具体函数。理解 Objective C 的 Runtime 机制,可以帮助我们更好的了解这门语言,适当的时候还能对语言进行扩展,从系统层面解决项目中的一些设计或技术问题。

Runtime 实际上是一个库,这个库使我们可以在程序运行时动态的创建对象、检查对象,修改类和对象的方法。Apple 和 GNU 各自维护一个开源的代码库,Apple 的开源代码库可以在官网源码中下载,GNU 的开源代码库可以在 GitHub 上下载

Runtime 概念解析

实例(objc_object)

#objc.h
/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;

#objc-private.h
typedef struct objc_object *id;

struct objc_object {
private:
    isa_t isa;
    //忽略结构体的方法
};

在objc工程中objc_object的定义有两个地方,这两个地方的定义基本是一致的,即一个isa指针,指向一个Class。

类对象(objc_class)

Objective C 类是由Class类型来定义的,它实际上是一个指向objc_class结构体的指针。

#objc.h
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// A pointer to an instance of a class.
typedef struct objc_object *id;

#objc-private.h
typedef struct objc_class *Class;
typedef struct objc_object *id;

#Object.mm
typedef struct objc_class *Class;
typedef struct objc_object *id;

在objc工程中查找Class的定义,可以看到三个文件中对Class有定义,且定义都是一致的,都是一个指向objc_class结构体的指针。

查看objc_class的定义

#runtime.h
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;
    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;
/* Use `Class` instead of `struct objc_class *` */

#objc-runtime-new.h
struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    //忽略结构体的方法
};

#objc-runtime-old.h
struct objc_class : objc_object {
    Class superclass;
    const char *name;
    uint32_t version;
    uint32_t info;
    uint32_t instance_size;
    struct old_ivar_list *ivars;
    struct old_method_list **methodLists;
    Cache cache;
    struct old_protocol_list *protocols;
    // CLS_EXT only
    const uint8_t *ivar_layout;
    struct old_class_ext *ext;
    //忽略结构体的方法
};

在objc工程中查找objc_class的定义,可以看到三个文件中对objc_class有定义,在objc-runtime-new.h文件和objc-runtime-old.h文件中都可以看到,objc_class继承自objc_object。

根据上面objc_object的定义,可以看到两者的差别在于class_data_bits_t,

#objc-runtime-new.h

// class_data_bits_t is the class_t->data field (class_rw_t pointer plus flags)
// The extra bits are optimized for the retain/release and alloc/dealloc paths.

struct class_data_bits_t {
    friend objc_class;

    // Values are the FAST_ flags above.
    uintptr_t bits;

public:
    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    //忽略结构体的方法
};

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t version;
    uint16_t witness;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;

#if SUPPORT_INDEXED_ISA
    uint32_t index;
#endif

    //忽略结构体的方法
};

class_data_bits_t结构体的data()方法,可以获取一个class_rw_t结构体的指针,结合class_rw_t的定义,可以看出在objc-runtime-new.h和objc-runtime-old.h中objc_class的定义基本是一致的,与objc_class暴露出来的参数一致。objc-runtime-new.h中的定义主要是兼容swift。

元类(Meta Class)

Meta Class(元类) 就是一个类对象所属的 类。一个对象所属的类叫做类对象,而一个类对象所属的类就叫做元类。

Runtime 中把类对象所属类型就叫做 Meta Class(元类),用于描述类对象本身所具有的特征,而在元类的 methodLists 中,保存了类的方法链表,即所谓的「类方法」。并且类对象中的 isa 指针 指向的就是元类。每个类对象有且仅有一个与之相关的元类。

MetaClass

Meta Class的这个图经常看到,这个图的逻辑怎么来的呢?
在objc工程中,我们看一下Meta Class相关的内容


#objc-runtime-new.h
struct objc_class : objc_object {
    ...
    bool isMetaClass() {
        ASSERT(this);
        ASSERT(isRealized());
#if FAST_CACHE_META
        return cache.getBit(FAST_CACHE_META);
#else
        return data()->ro->flags & RO_META;
#endif
    }

    // Like isMetaClass, but also valid on un-realized classes
    bool isMetaClassMaybeUnrealized() {
        return bits.safe_ro()->flags & RO_META;
    }

    // NOT identical to this->ISA when this is a metaclass
    Class getMeta() {
        if (isMetaClass()) return (Class)this;
        else return this->ISA();
    }

    bool isRootClass() {
        return superclass == nil;
    }
    bool isRootMetaclass() {
        return ISA() == (Class)this;
    }
    ...
};

#objc-runtime-old.h
struct objc_class : objc_object {
    ...
    bool isRootClass() {
        return superclass == nil;
    }

    bool isRootMetaclass() {
        return ISA() == (Class)this;
    }

    bool isMetaClass() {
        return info & CLS_META;
    }

    // NOT identical to this->ISA() when this is a metaclass
    Class getMeta() {
        if (isMetaClass()) return (Class)this;
        else return this->ISA();
    }
    ...
};

从代码可以看出,实例(objc_object)的isa指针指向类对象。

类对象获取Meta Class时,先判定自身是否是MetaClass,如果是返回自身,否则返回自身的isa指针。

类对象的isa指针指向了元类,super_class指针指向了父类的类对象,而元类的super_class指针指向了父类的元类,那元类的isa指针又指向了自己。

NSObject是Objective C中最基础的类,所以NSObject的isa指针指向自己,super_class指针指向nil。

所以我们可以得出实例对象、类、元类之间的关系:
每一级中的实例对象的 isa 指针指向了对应的类对象,而类对象的 isa 指针指向了对应的元类。而所有元类的 isa 指针最终指向了 NSObject 元类,因此 NSObject 元类也被称为根元类。

元类的 isa 指针和父类元类的 isa 指针都指向了根元类。而根元类的 isa 指针又指向了自己。

类对象的父类指针 指向了 父类的类对象,父类的类对象 又指向了 根类的类对象,根类的类对象 最终指向了 nil。

元类 的 父类指针 指向了 父类对象的元类。父类对象的元类 的 父类指针指向了 根类对象的元类,也就是 根元类。而 根元类 的 父亲指针 指向了 根类对象,最终指向了 nil。

Method(objc_method)

#runtime.h

/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

/// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
#endif

/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;

struct objc_method {
    SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;
    char * _Nullable method_types                            OBJC2_UNAVAILABLE;
    IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;

struct objc_method_list {
    struct objc_method_list * _Nullable obsolete             OBJC2_UNAVAILABLE;

    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}

上面是objc工程中关于Method的内容,可以看出Method是一个指向objc_method结构体的指针。objc_method结构体中包含method_name,method_types,method_imp三个参数。
method_name是一个SEL,SEL是一个指向objc_selector结构体的指针,但是objc工程中,没有objc_selector的定义。不过通过测试,我们可以看出SEL是一个保存方法名的字符串。
method_types是一个字符串,用来存储方法的参数类型和返回值类型。
IMP是一个函数指针,指向方法的具体实现。

Runtime 消息机制

消息传递(方法调用)

在Objective-C中,如果向某对象传递消息,就会使用动态绑定机制来决定需要调用的方法。在底层,所有方法都是普通的C语言函数,当对象收到消息之后,究竟调用哪一个方法则完全于运行时决定。

消息传递机制中的核心函数是objc_msgSend,其定义如下:

#message.h

/* Basic Messaging Primitives
 *
 * On some architectures, use objc_msgSend_stret for some struct return types.
 * On some architectures, use objc_msgSend_fpret for some float return types.
 * On some architectures, use objc_msgSend_fp2ret for some float return types.
 *
 * These functions must be cast to an appropriate function pointer type 
 * before being called. 
 */
#if !OBJC_OLD_DISPATCH_PROTOTYPES
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wincompatible-library-redeclaration"
OBJC_EXPORT void
objc_msgSend(void /* id self, SEL op, ... */ )
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

OBJC_EXPORT void
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
#pragma clang diagnostic pop
#else
/** 
 * Sends a message with a simple return value to an instance of a class.
 * 
 * @param self A pointer to the instance of the class that is to receive the message.
 * @param op The selector of the method that handles the message.
 * @param ... 
 *   A variable argument list containing the arguments to the method.
 * 
 * @return The return value of the method.
 * 
 * @note When it encounters a method call, the compiler generates a call to one of the
 *  functions \c objc_msgSend, \c objc_msgSend_stret, \c objc_msgSendSuper, or \c objc_msgSendSuper_stret.
 *  Messages sent to an object’s superclass (using the \c super keyword) are sent using \c objc_msgSendSuper; 
 *  other messages are sent using \c objc_msgSend. Methods that have data structures as return values
 *  are sent using \c objc_msgSendSuper_stret and \c objc_msgSend_stret.
 */
OBJC_EXPORT id _Nullable
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
/** 
 * Sends a message with a simple return value to the superclass of an instance of a class.
 * 
 * @param super A pointer to an \c objc_super data structure. Pass values identifying the
 *  context the message was sent to, including the instance of the class that is to receive the
 *  message and the superclass at which to start searching for the method implementation.
 * @param op A pointer of type SEL. Pass the selector of the method that will handle the message.
 * @param ...
 *   A variable argument list containing the arguments to the method.
 * 
 * @return The return value of the method identified by \e op.
 * 
 * @see objc_msgSend
 */
OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
#endif

objc_msgSend函数会根据id(objc_object)和SEL来调用适当的方法。

  • 首先通过objc_object的isa指针,找到对应的objc_class
  • 在objc_class的objc_method_list中查找SEL
  • 如果上一步没有找到SEL,则沿着继承体系继续向上查找
  • 如果找到就执行对应的实现IMP,否则执行消息转发

可以看出执行一个方法调用似乎需要很多步骤,所以objc_msgSend 会将匹配到的结果缓存到objc_class的cache中,后续还向该类发送相同的消息调用时,直接在cache中查找。
dsASERT
上面只是部分消息调用的过程,一些边界情况,则由Objective-C运行环境中的另一些函数处理:

  • objc_msgSend_stret
  • objc_msgSend_fpret
  • objc_msgSendSuper

消息转发

消息转发分为两个阶段。第一阶段先看对象所属的类,是否能动态添加方法,以处理当前未找到的SEL,这个过程就是动态方法解析。第二阶段是分为两步,首先,看有没有其他对象能处理这条消息,若有,则把消息转给那个对象,消息转发过程结束,一切如常。若没有,则启动完整的消息转发机制,将把消息有关的全部细节都封装到NSInvocation对象中,再给对象最后一次机会,令其设法解决当前还未处理的这条消息。

消息转发

动态方法解析

Objective-C 运行时会调用 +resolveInstanceMethod: 或者 +resolveClassMethod:,让你有机会提供一个函数实现。前者在 对象方法未找到时 调用,后者在 类方法未找到时 调用。我们可以通过重写这两个方法,添加其他函数实现,并返回 YES, 那运行时系统就会重新启动一次消息发送的过程。

消息接受者重定向

如果上一步中 +resolveInstanceMethod: 或者 +resolveClassMethod: 没有添加其他函数实现,运行时就会进行下一步:消息接受者重定向。

如果当前对象实现了 -forwardingTargetForSelector: 或者 +forwardingTargetForSelector: 方法,Runtime 就会调用这个方法,允许我们将消息的接受者转发给其他对象。

完整的消息转发机制

如果经过消息动态解析、消息接受者重定向,Runtime 系统还是找不到相应的方法实现而无法响应消息,Runtime 系统会利用 -methodSignatureForSelector: 或者 +methodSignatureForSelector: 方法获取函数的参数和返回值类型。

如果 methodSignatureForSelector: 返回了一个 NSMethodSignature 对象(函数签名),Runtime 系统就会创建一个 NSInvocation 对象,并通过 forwardInvocation: 消息通知当前对象,给予此次消息发送最后一次寻找 IMP 的机会。
如果 methodSignatureForSelector: 返回 nil。则 Runtime 系统会发出 doesNotRecognizeSelector: 消息,程序也就崩溃了。
所以我们可以在 forwardInvocation: 方法中对消息进行转发。

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