ios runtime重要性_iOS开发Runtime的理解与应用

一、Runtime概念

RunTime简称运行时,其中最主要的是消息机制。

对于C语言,函数的调用在编译的时候会决定调用哪个函数,编译完成之后直接顺序执行,无任何二义性。

OC的函数调用成为消息发送,属于动态调用过程。在编译的时候并不能决定真正调用哪个函数(事实证明,在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。而C语言在编译阶段就会报错)。

只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。

其动态性体现在三个方面:

1.动态类型:

即运行时再决定对象的类型。简单说就是id类型,任何对象都可以被id指针所指,只有在运行时才能决定是什么类型。像内置的明确的基本类型都属于静态类型(int、NSString等)。静态类型在编译的时候就能被识别出来。所以,若程序发生了类型不对应,编译器就会发出警告。而动态类型就编译器编译的时候是不能被识别的,要等到运行时(run time),即程序运行的时候才会根据语境来识别。所以这里面就有两个概念要分清:编译时跟运行时。

2.动态绑定:

基于动态类型,在某个实例对象被确定后,其类型便被确定了。该对象对应的属性和响应的消息也被完全确定,这就是动态绑定。比如我们一般向一个NSObject对象发送-respondsToSelector:或者 -instancesRespondToSelector:等来确定对象是否可以对某个SEL做出响应,而在OC消息转发机制被触发之前,对应的类 的+resolveClassMethod:和+resolveInstanceMethod:将会被调用,在此时有机会动态地向类或者实例添加新的方 法,也即类的实现是可以动态绑定的;isKindOfClass也是一样的道理。

3.动态加载:

所谓动态加载就是我们做开发的时候icon图片的时候在Retina设备上要多添加一个张@2x的图片,当设备更换的时候,图片也会自动的替换。

二、Runtime数据结构

1.Class

Objective-C类是由Class类型来表示,Class 其实是指向 objc_class 结构体的指针

typedef struct objc_class *Class;

我们可以从里面看到类的定义

struct object_class{

Class isa OBJC_ISA_AVAILABILITY;

#if !__OBJC2__

Class super_class OBJC2_UNAVAILABLE; // 父类

const char *name OBJC2_UNAVAILABLE; // 类名

long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0

long info OBJC2_UNAVAILABLE; // 类信息,供运行期使用的一些位标识

long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小

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

struct objc_method_list *methodLists OBJC2_UNAVAILABLE; // 方法定义的链表

struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法缓存

struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表

#endif

}OBJC2_UNAVAILABLE;

2.id

id 是一个结构体指针类型,它可以指向 Objective-C 中的任何对象。从中可以看出id其实是指向objc_object结构体的指针。objc_object只有一个成员变量 isa,对象可以通过 isa 指针找到其所属的类,这也是为什么id可以表示任意对象的原因。isa 是一个 Class 类型的成员变量。

注意:在KVO中,isa在运行时会被修改,指向一个中间类,对于编译器而言,isa的指向才是最真实的类型。

/// 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;

当我们向一个Objective-C对象发送消息时,运行时库会根据

实例对象的isa指针找到这个实例对象所属的类。Runtime库会在类的方法列表由super_class指针找到父类的方法列表直至根类NSObject中去寻找与消息对应的selector指向的方法,找到后即运行这个方法。

3.元类(meta-Class)

类自身也是一种对象,可以叫做“类对象”。类对象包含一个指向其类的一个isa指针( Class _Nonnull isa )

为了调用类方法,这个类的isa指针必须指向一个包含这些类方法的一个objc_class结构体。这就引出了meta-class的概念,meta-class中存储着一个类的所有类方法。

所以,调用类方法的这个类对象的isa指针指向的就是meta-class。当我们向一个对象发送消息时,runtime会在这个对象所属的这个类的方法列表中查找方法;而向一个类发送消息时,会在这个类的meta-class的方法列表中查找。

下图是一个经典的类及相应meta-class类的一个继承体系图解:

image.png

从图中可以看出:

1.每个实例对象的类都是类对象,每个类对象的类都是元类对象,每个元类对象的类都是根元类(root meta class的isa指向自身)。即任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己的所属类,而基类的meta-class的isa指针是指向它自己。

2.类对象的父类最终继承自根类对象NSObject,NSObject的父类为nil

3.元类对象(包括根元类)的父类最终继承自根类对象NSObject

三、方法和消息

1.SEL

typedef struct objc_selector *SEL;

SEL sel1 = @selector(func1);

SEL sel2 = NSSelectorFromString(func2);

SEL本质上是一个指向方法的指针。Objective-C在编译时,会依据每一个方法的名字、参数序列,生成一个唯一的整型标识即SEL,每一个方法都对应着一个SEL。所以即使返回值类型或参数类型不同,方法名相同也会报错。

2.IMP

id (*IMP)(id, SEL,...)

IMP的本质是函数指针,指向方法实现的地址,直接通过IMP就可以找到各个方法。这样效率更高,因为绕过了消息传递阶段,直接定位。

SEL就是为了查找方法的最终实现IMP的。由于每个方法对应唯一的SEL,因此我们可以通过SEL方便快速准确地获得它所对应的IMP。

消息发送和转发流程可以概括为:消息发送(Messaging)是 Runtime 通过 selector 快速查找 IMP 的过程,有了函数指针就可以执行对应的方法实现;消息转发(Message Forwarding)是在查找 IMP 失败后执行一系列转发流程的慢速通道,如果不作转发处理,则会打日志和抛出异常。

3.Method

typedef struct objc_method *Method

struct objc_method{

SEL method_name OBJC2_UNAVAILABLE; // 方法名

char *method_types OBJC2_UNAVAILABLE;

IMP method_imp OBJC2_UNAVAILABLE; // 方法实现

}

可以看出method包含SEL和IMP,在实现方法交换时,主要原理就是交换SEL和IMP的映射关系。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值