ios runtime重要性_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: 方法中对消息进行转发。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值