本文讲述 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: 方法中对消息进行转发。