分享

iOS 运行时(Runtime)之 类和对象

 我爱你文摘 2019-09-22

Runtime简介

Objective-C runtime 运行 native code 。Java VM 运行 byte code。

原生码(native code)又称为机器码(machine code),是电脑的CPU可直接解读的数据,是计算机可以直接执行,执行速度最快的代码。

字节码(byte code)码是一种中间状态(中间码)的二进制代码(文件)。需要转译后才能成为机器码。

比如java:字节码在运行时通过JVM(JAVA虚拟机)做一次转换生成机器指令,因此能够更好的跨平台运行。

Objc Runtime其实是一个Runtime库,它基本上是用C和汇编写的,这个库使得C语言有了面向对象的能力。

在Runtime中,对象可以用C语言中的结构体表示,而方法可以用C函数来实现,另外再加上了一些额外的特性。这些结构体和函数被runtime函数封装后,让OC的面向对象编程变为可能。

当程序执行[object doSomething]时,会向消息接收者(object)发送一条消息(doSomething),runtime会根据消息接收者是否能响应该消息而做出不同的反应。

动态语言

静态类型语言:指在编译期间就去做数据类型检查,多数静态类型语言要求在使用变量之前必须声明数据类型。比如C#,Java。

动态类型语言:指在运行期间才去做数据类型检查,也就是说,用动态语言编程时,永远不用去给任何变量去指定数据类型。该语言会在你第一次给该变量赋值的时候,在内部把数据类型记录下来。JavaScript、ruby或者Python是典型的动态类型的语言。

OC语言,编译阶段并不能决定真正调用哪个函数,只要函数声明过即使没有实现也不会报错。

我们常说OC是一门动态语言,就是因为它总是把一些决定性的工作从编译阶段推迟到运行时阶段。OC代码的运行不仅需要编译器,还需要运行时系统(Runtime)来执行编译后的代码。

Runtime是一套底层纯C语言API,OC代码最终都会被编译器转化为运行时代码,通过消息机制决定函数调用方式,这也是OC作为动态语言使用的基础。

isa

在Objective-C中,任何类的定义都是对象。类和类的实例(对象)没有任何本质上的区别。任何对象都有isa指针。

isa:是类指针,之所以说isa是指针是因为Class其实是一个指向objc_class结构体的指针,而isa 是它唯一的私有成员变量,即所有对象都有isa指针(isa位置在成员变量第一个位置)

objc_class是个结构体,而且第一个成员变量也是isa指针,这个指针指向的Class不是指向自己而是metaclass(元类)

metaclass

每个实例对象也就是struct objc_object结构体它的isa的指针指向类对象Class,而Class里也有个isa的指针, 指向metaClass(元类)。

元类是类对象的类,类对象是元类的实例。

这时候相对于元类,objc_class就是元类的实例对象。

基于这种设计模式,不难发现:

  1. 调用 '+' 开头的类方法 是在调用元类的对象方法,元类保存了类方法的列表和成员变量
  2. 由于每个类有且只有一个,所以每个类对象都是其对应元类的单例
  3. 元类(metaClass)也是类,它也是对象。
  4. 元类也有isa指针,它的isa指针最终指向的是一个根元类(root metaClass)
  5. 根元类的isa指针指向本身,这样形成了一个封闭的内循环。
iOS 运行时(Runtime)之 类和对象

看图说话:

1. objc_object中的isa指的是对象的类。

2. objc_class中的isa指的是类的元类。

3. superClass是一层层集成的,到最后NSObject的superClass是nil。而NSObject的isa指向根元类,这个根元类的isa指向它自己,而它的superClass是NSObject,也就是最后形成一个环。

4. metaClass也是相互继承的。

5. 从objc_class结构体可以看出来,里面有个isa属性,还有个super_class属性,它俩都是指针,其实在objc_class的定义中也能看出来,每一个objc_class都有isa,但是不一定会有super_class。

重点总结:

meta-class也是一个类,也可以向它发送一个消息,那么它的isa又是指向什么呢?为了不让这种结构无限延伸下去,Objective-C的设计者让所有的meta-class的isa指向基类的meta-class,以此作为它们的所属类。即,任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己的所属类,而基类的meta-class的isa指针是指向它自己。这样就形成了一个完美的闭环。

方法在 objc-runtime-new.h中定义:

struct method_t { SEL name; const char *types; IMP imp; struct SortBySELAddress : public std::binary_function { bool operator() (const method_t& lhs, const method_t& rhs) { return lhs.name < rhs.name;="" }="">

我们说下上面代码中的SEL和IMP。

SEL:表示该方法的名称, IMP: 指向该方法的具体实现的函数指针。

SEL 的含义:

typedef struct objc_selector *SEL;

它是一个指向 objc_selector 指针,表示方法的名字/签名。如下所示,打印出 selector,会打印出来方法的名称。

不同类的实例对象performSelector相同的 selector 时,会在各自的消息选标(selector)/实现地址(address) 方法链表中根据 selector 去查找具体的方法实现IMP, 然后用这个方法实现去执行具体的实现代码。这是一个动态绑定的过程,在编译的时候,我们不知道最终会执行哪一些代码,只有在执行的时候,通过selector去查询,我们才能确定具体的执行代码。

IMP 的含义:

在前面我们也看到 IMP 的定义为:

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

id是一个指向 objc_object 结构体的指针,任何继承自 NSObject 的类对象都可以用id 来指代,因为 NSObject 的第一个成员实例就是isa。

IMP 是一个函数指针,这个被指向的函数包含一个接收消息的对象id(self 指针), 调用方法的选标 SEL (方法名),以及不定个数的方法参数,并返回一个id。也就是说 IMP 是消息最终调用的执行代码,是方法真正的实现代码 。我们可以像在C语言里面一样使用这个函数指针。

NSObject 类中的methodForSelector:方法就是这样一个获取指向方法实现IMP 的指针,methodForSelector:返回的指针和赋值的变量类型必须完全一致,包括方法的参数类型和返回值类型。

IMP实际上是一个函数指针,指向方法实现的地址。

Method用于表示类定义中的方法,则定义如下:

typedef struct objc_method *Methodstruct objc_method{ SEL method_name OBJC2_UNAVAILABLE; // 方法名 char *method_types OBJC2_UNAVAILABLE; IMP method_imp OBJC2_UNAVAILABLE; // 方法实现}

我们可以看到该结构体中包含一个SEL和IMP,实际上相当于在SEL和IMP之间作了一个映射。有了SEL,我们便可以找到对应的IMP,从而调用方法的实现代码。

iOS 运行时(Runtime)之 类和对象

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多