格而知之6:我所理解的Runtime(1)

基本简介

1、根据官方文档,OC有一个特性:它会尽可能把一些决定从编译时和链接时推迟到运行时才处理,所以这门语言需要的就不只是一个编译器,它还需要一个runtime系统来处理那些已经被编译过的代码。

2、runtime有两种:legacy runtime和modern runtime,区别在于:

(1)、在legacy runtime中,如果你修改了一个类的实例变量之后,你需要重新编译一下;

(2)、在modern runtime中,在这种情况下你无需再重新编译一次。

3、OC的程序和runtime系统有三个层次的交互方式:

(1)、大部分时候,当你直接使用OC代码的时候,runtime系统便在运作了。这其中一个最主要的功能便是消息发送(Messaging);

(2)、使用一些NSObject类的方法,比如description或isKindOfClass: 、isMemberOfClass:等方法(其实大部分的类都是继承自NSObject,都可以使用这些方法,也有一些继承自其它类,比如继承自NSProxy类);

(3)、直接调用runtime提供的方法。

消息发送(Messaging)的概念

4、在讨论消息发送之前,我们先要明白一个概念:方法的selector是什么?

方法的selector是根据这个方法的方法名计算得出的一个key,通过这个key可以得到反推出这个方法的方法名。由于selector是根据方法名得出的,所以同名的方法的selector都是相同的。

而在一个类中,为了让一个selector可以唯一标识出一个方法,于是在同一个类里就不允许有两个同名的方法,即使参数不同也不行。(所以OC没有方法重载)

方法除了selector之外,另外还有一个组成部分是方法实现(procedure ,也即是method implementation),指的是方法的实现代码。

所以可以这么理解:方法就是它的selector和实现的统称。

同时需要注意的是,一个方法的selector和实现之间并不是固定对应的,消息发送的过程其实就是通过方法的selector找到它的实现的过程。

5、消息发送的基本流程:

在OC里面,当我们“调用”方法的时候,使用的是:

[receiver message]

这样的语法,这句代码其实并不会立刻就去执行message方法的代码,在这句代码和message方法的代码之间,有一个消息发送(Messaging)的过程。

编译器把这一句代码编译成了:

objc_msgSend(receiver, selector)

如果方法有多个参数的话,会连同参数被编译成:

objc_msgSend(receiver, selector, arg1, arg2, ...)

然后这个objc_msgSend()函数执行的过程便是消息发送的过程了。

消息其实就是objc_msgSend()函数的所有参数的并称,可以理解为:消息(Message)就是方法调用者、方法的selector和方法的参数等细节的集合。

消息发送的过程其实就是objc_msgSend()函数会根据receiver和selector参数找到对应的方法实现代码(procedure ,也即是method implementation)并执行它的过程。

在这个过程中,objc_msgSend()函数是如何找到对应的类,找到对应的方法实现的呢?并且如果在查找的过程中发生了意外的话,它又会怎么处理呢?

在研究这些问题之前,我们先要了解一下runtime相关的一些数据结构。

runtime相关的数据结构

6、我们从objc_msgSend()这个方法的定义入手,来研究一下这些数据结构,objc_msgSend()的定义如下:

id objc_msgSend(id self, SEL op, ...)

它有两个主要的参数,一个是id类型的,一个SEL类型的。

(1)、首先来看SEL这个类型,我们可以找到它的定义如下:

没有找到struct objc_selector进一步的定义,但是我们通过字面可以知道这个结构体便是方法,或者是能够唯一标识出这个方法的一些信息,而SEL作为一个指向selector的指针,便可理解为指向某个方法的指针了;

(2)、objc_msgSend()的另一个主要参数是id类型的,我们可以找到id类型的定义如下:

所以id其实是一个指向struct objc_object结构体的指针,而struct objc_object这个结构体包含了一个成员isa,这个成员的数据类型是Class,那么接下来就来看看Class是什么;

(3)、Class的定义如下:

我们可以知道,Class是一个指向struct objc_class结构体的指针,再进一步找到struct objc_class结构体的定义,如下:

可以看到struct objc_class结构体包含了很多成员,我们一个一个来查看这些成员。

7、struct objc_class:

(1)、首先我们可以神奇地发现,struct objc_class结构体的第一个成员的数据类型竟然也是Class,由上面我们已经知道Class是一个指向struct objc_class结构体的指针,所以struct objc_class结构体的第一个成员也是一个同样叫做isa 的struct objc_class结构体。

这说明了,类本身也是某种类的实例。这个先按下不表,在后文(9)中再做讨论;

(2)、struct objc_class结构体(以下直接称为“类”)的第二个成员仍然是一个Class类型的数据super_class,表示这个结构体对象对应的父类;

(3)、version和info都是存储着类相关的信息,当某个类的实例变量发生变化的时候,它的version的内容会跟着发生变化;

(4)、instance_size是这个类生成的实例对象的大小;

(5)、成员ivars指针指向了struct objc_ivar_list结构体类型的数据,我们可以找到struct objc_ivar_list结构体这种数据的定义:

可以看到其中最主要的成员是一个由struct objc_ivar结构体组成的数组ivar_list[],它便是这个类所包含的所有实例变量组成数组。

我们还可以进一步查看到struct objc_ivar结构体的定义:

所以其实一个struct objc_ivar结构体便是一个成员变量;

(6)、成员methodLists和ivars类似,我们先看一下struct objc_method_list这种数据类型的定义:

结构和struct objc_ivar_list基本相似,主要内容也是一个数组,由struct objc_method类型的数据组成,我们再看一下struct objc_method的定义:

可以发现struct objc_method类型的数据便是其实便是方法了。这个结构体最主要的成员是一个IMP类型的数据method_imp,它其实是一个指向方法实现代码的指针。

同时,可以注意到成员methodLists是一个二级指针,这决定了它可以更改指向的*methodLists,即是可以修改方法列表,这也是Category可以添加方法的原因;

(7)、成员cache用来存储经常调用的method,当对某个对象调用方法时,runtime会根据对象的isa指针找到对象对应的类,然后首先在类的cache中查到要调用的方法,当cache中找不到之后,才会去methodLists中查找;

(8)、成员protocols标识这个类遵循的协议;

(9)、前面说到,类本身也是某种类的实例,我们从Class第一个成员isa也是一个Class类型的的数据的出这个结论的。

类所属的类,称为元类(Meta Class),元类存储着一个类的所有类方法,类是这个元类的实例。当我们调用类方法的时候,其实把这个类当做一个实例向它发送消息:当我们向一个对象发送消息时,runtime会在这个对象所属的这个类的方法列表中查找方法;而向一个类发送消息时,会在这个类的元类的方法列表中查找。

再继续深究下去,发现元类也是一个类,那么它的isa指针又该指向哪里呢?这样一直延伸下去会无穷无尽,所以所有元类的isa指针会被指向同一个类——基类(Root Class)的元类,基类的元类的isa指针又指向了它自己,形成了一个闭环。

需要清楚一个概念:元类(meta class)和父类(super class)是两个不同的概念,一般一个类都会有个一个元类和一个父类。

元类是这个类作为一个对象看待时,它所属的类;父类是这个类作为类看待时,它继承而来的类。

它们的关系如下图:

可以注意到,基类(Root Class)其实就是NSObject,它的superclass指向nil。NSObject的元类的父类指针指向NSObject,最终在顶部形成了一个回路。

时间: 2024-10-11 06:12:09

格而知之6:我所理解的Runtime(1)的相关文章

理解Objective-C Runtime(四)Method Swizzling

Objective-C对象收到消息之后,究竟会调用何种方法需要在运行期间才能解析出来.那你也许会问:与给定的选择子名称相应的方法是不是也可以在runtime改变呢?没错,就是这样.若能善用此特性,则可发挥出巨大优势,因为我们既不需要源代码,也不需要通过继承子类来覆写方法就能改变这个类本身的功能.这样一来,新功能将在本类的所有实例中生效,而不仅限于覆写了相关方法的那些子类实例.此方案就是大名鼎鼎的「method swizzling」,中文常称之为『方法调配』或『方法调和』或『方法混合』. Meth

格而知之16:我所理解的Block(2)

11.那么Block到底是怎么实现的呢?试一试通过将Block 的代码转换成普通C语言代码来查看它的实现过程. 要将OC代码转换成C语言代码,可以使用clang编译的一个命令: 通过这个命令能把指定文件中的OC代码改写成C++代码(其中主要部分还是普通的C语言代码),通过这些代码就能看到Block是如何使用C++语言实现的. 12.首先编写一个最简单的Block,没有返回值和参数列表,执行它会输出“Shayne Yeorg”.代码如下: 然后使用11的命令转换这个文件,可以得到文件main.cp

格而知之7:我所理解的Runtime(2)

消息发送(Messaging) 8.以上便是runtime相关的一些数据结构,接下来我们回看一开始的疑问: objc_msgSend()函数在执行的过程中是如何找到对应的类,找到对应的方法实现的呢? 这就是消息发送(messaging)的处理过程了: (1).对于上文的Class的数据结构的描述,官方文档只简略了把它们归纳成了两部分:一个指向其父类的指针和一个方法调用表(这个Class的所有方法的selector和实现代码所在地址的关联表): (2).当某个消息被发送到一个对象之后(即对象执行某

格而知之15:我所理解的Block(1)

1.Block 本质上是一个struct结构体,在这个结构体中,最重要的成员是一个函数(当然除函数外还有其他重要的成员). 2.在开始解析Block之前,首先来回顾一下Block的格式.Block相关的格式有2个: (1).Block对象的格式: (2).Block变量的格式: 3.对于Block对象,它有几种常见的格式: (1).一个完整的Block对象的格式如下: 比如这个Block对象: 可以发现,完整的Block对象和函数的定义非常相似,比如这个函数: 两者之间的区别仅仅是:Block对

格而知之8:我所理解的Runtime(3)

关联对象 14.使用Category对类进行拓展的时候,只能添加方法,而不适合添加属性(可以添加属性,也可以正常使用get方法和set方法,只是不会自动生成以下划线开头命名的成员变量). 可以通过关联对象(Associated Objects)来在Category里添加一个关联对象,然后将这个对象当做属性来看待使用,制造出“在Category中添加属性”的效果. (1).假设要在NSObject类的Category中添加一个关联对象,处理方法如下: 首先在@interface里照常添加属性ass

格而知之16:我所理解的Block(3)

23.在前文中的例子中,Block结构体里的isa指针还没有详细讲解,这个指针都被置向了_NSConcreteStackBlock,它标识了Block的类型. 其实除了_NSConcreteStackBlock这个类型外,Block还有其他的类型,这些类型总共有3种: (1)._NSConcreteStackBlock (2)._NSConcreteMallocBlock (3)._NSConcreteGlobalBlock 24.这3种类型分别表示了Block对象存储的区域,如下图: 25.编

格而知之9:一些关于GCD的笔记

1.最近在重读当年刚开始学习多线程时的笔记,发觉其中有一些地方还是比较容易模糊,于是整理这篇笔记记录一下. 执行方式和队列 2.队列用来存放管理要执行的任务,它分为并发队列(Concurrent Dispatch Queue)和串行队列(Serial Dispatch Queue): 并发队列:队列内的任务允许被分配给多条线程同时执行: 串行队列:队列内的任务只能被放在一条线程内顺序执行. 3.执行方式分为异步(Asynchronization)和同步(Synchronization): 异步:

格而知之2:UIView的autoresizingMask属性探究

UIView的autoresizingMask属性,是用在当一个UIView实例的父控件的尺寸发生变化时,来自动调整UIView实例在父控件中的位置与尺寸的.autoresizingMask属性是一个枚举值,它的枚举成员如下: 它通过使UIView的上.下.左.右.宽度或高度自动变化来调整.下面分几种情况来讨论这个属性,假设父控件为backgroundView,子控件为subView: 1.当没有使用autoresizingMask属性或autoresizingMask属性的值为UIViewAu

格而知之1:UIButton中imageView和titleLabel的位置调整

在使用UIButton时,有时候需要调整按钮内部的imageView和titleLabel的位置和尺寸.在默认情况下,按钮内部的imageView和titleLabel的显示效果是图片在左文字在右,然后两者紧挨在一起构成组合居中显示.如下图: 我们可以使用setImageEdgeInsets:和setTitleEdgeInsets:方法来调整两者的位置.在使用这两个方法之前,我们首先要将imageView和titleLabel定位在UIButton的左上角位置,方便参照坐标调节位置.使用以下语句