OC-Runtime(部分)

2018-05-25  本文已影响0人  帽子和五朵玫瑰

OC-Runtime

介绍macOS Objective-C运行时库支持函数和数据结构。

runtime是属于OC的底层,是一套比较底层的纯C语言API, 属于1个C语言库, 包含了很多底层的C语言API,可以进行一些非常底层的操作(用OC是无法现实的, 不好实现)。 在我们平时编写的OC代码中, 程序运行过程时, 其实最终都是转成了runtime的C语言代码, runtime算是OC的幕后工作者。

Overview

Objective-C runtime 是一个runtime library,它为Objective-C语言的动态属性提供支持,并且由所有Objective-C应用程序链接。 Objective-C运行时库支持函数在/usr/lib/libobjc.A.dylib中的共享库中实现。

在Objective-C编程时,您通常不需要直接使用Objective-C runtime library。 此API主要用于在Objective-C和其他语言之间开发桥接层,或者用于低级调试。

Objective-C运行时库的macOS实现对于Mac来说是独一无二的。 对于其他平台,GNU编译器集合使用类似的API提供了不同的实现。 本文档仅涵盖macOS实现。

底层的Objective-C运行时API在OS X 10.5版本中显着更新。 许多功能和所有现有的数据结构都被新的功能所取代。 旧的功能和结构在32位不推荐使用,在64位模式下不使用。 即使在64位模式类数,协议计数,每个类的方法,每个类的ivars,每个方法的参数,每个方法的sizeof(所有参数)以及类的版本号中,API也将多个值限制为32位整数。 此外,新的Objective-C ABI(此处未描述)进一步将sizeof(anInstance)限制为32位,并将其他三个值限制为24位 - 每个类的方法,每个类的ivars和sizeof(单个ivar)。 最后,过时的NXHashTable和NXMapTable仅限于40亿个项目。

String encoding

运行时API中的所有char * 应被视为具有UTF-8编码。

前言

OC是一种面向对象的动态语言,作为初学者可能大多数人对面向对象这个概念理解的比较深,而对OC是动态语言这一特性了解的比较少。那么什么是动态语言?动态语言就是在运行时来执行静态语言的编译链接的工作。这就要求除了编译器之外还要有一种运行时系统来执行编译等功能。OC中这个系统就是runtime。

一、版本

OC中的运行时分为两个版本——Modern Runtime和Legacy Runtime。现在的运行时与遗留的运行时区别在于:遗留的运行时在改变一个类的结构时,你必须继承它并重新编译。而现在的运行时可以直接编译。

OC程序与运行时系统交互分为三个不同等级:

通过OC源代码

通过定义在Foudation框架中NSObject中的方法

通过直接调用运行时的函数

1. 通过OC源代码

在大多数情况下,运行时会自动在幕后工作。你使用它只是编写和编译OC源代码。

当你编译的代码包含OC中的类和方法时,编译器创建数据结构和函数调用,实现语言的动态特性。数据结构捕获类,分类和协议中声明的信息。其中包括在OC中讨论类和协议对象的定义,以及从源代码中提取出来方法选择器,实例模板和其他信息。运行时的主要功能就是传递消息,正如消息传递中所描述的那样。它通过源代码消息表达式来来调用。

2. 通过NSObject中定义的方法

在Cocoa中,大多数对象是NSObject类的子类对象,所以大多数对象继承了他定义的方法(NSProxy类除外)。因此它的方法建立每个实例,每个类对象的行为。然而在少数情况下,NSOject只定义了一个怎样去做的模板,它本身不提供所有必要的代码(抽象类?)

例如,NSObject定义了一个返回一个描述类内容的字符串的实例方法。这主要用于调试GDB对象打印命令从这各类中打印的字符串。NSObject的方法实现中不知道类中包含什么内容,所以它返回一个包含对象名和地址的字符串。NSObject的子类可以实现这个方法返回更多的细节。例如,Foundation中NSSArray返回一个它包含对象的描述列表。

NSObject方法的一些简单的查询的运行时系统信息。这些方法允许对象自省(自我查找)。这种方法的例子是类方法,例如isKindOfClass:问一个对象来确定它的类:isMemberOfClass测试对象在继承结构中的层次位置,respondsToSelector,这表明一个对象是否能接受特定的消息,conformsToProtocol:确定对象是否实现在特定协议中定义的方法,methodForSelector:提供方法实现的地址。像这样的方法给予了对象自省的能力。

3. 直接调用运行时的函数

运行时系统是一个定义在/usr/include/objc目录下的,有一个公共接口在它头文件中包含一系列方法和数据结构动态共享库。这里面许多方法允许你使用C语言重复编译器在你写OC代码时是怎样工作的。其他基础功能形式通过NSObject类的方法来导出。当OC中不需要时,这些方法使开发runtime的其他接口,生产出增强开发环境的工具成为可能。然而,一小些运行时函数只能在编写OC程序时有用。所有的功能都记录在Objective-C Runtime Reference.中。

二、消息传递机制

这一部分描述了如何把消息表达式转换成objc_msgSend函数调用,怎样通过名字找到方法。然后解释了如果你需要的话怎么通过objc_msgSend来绕过动态绑定。

1. objc_msgSend 功能

在OC中,消息不跟方法实现绑定直到运行时。编译器将消息表达式 [receiver message] 转化成一个消息传递函数objc_msgSend。这个函数将接收者和在消息中提到的方法名(方法选择器)作为他的两个主要参数:objc_msgSend(receiver, selector)。消息中任何参数也交给objc_msgSend:objc_msgSend(receiver, selector, arg1, arg2, …)。

消息传递函数为动态绑定做了所有必须的事情:

它首先发现方法选择器指向的程序(方法的实现)。因为相同的方法可以被不同的类分别实现。这个准确的程序依赖于接收者的类。

然后调用程序,通过接收对象(指针指向他的数据)为方法传递指定的参数。

最后,当他返回值的时候它传递程序的返回值。

提示:编译器对消息传递函数生成调用,在你的代码中不要直接调用。

消息传递机制的关键在于编译器对每个类和对象的结构的构建,每个类结构包含两个基本元素:指向父类的指针和类调度表。这个表罗列了他们定义的有明确类特征的方法的地址的方法选择器。例如,setOrigin::方法的选择器与setOrigin::方法的实现联系起来,展示方法的选择器关联展示的地址等等。

创建新对象时,分配内存,实例变量被初始化。首先在对象中有一个指向它的类结构的指针变量。这个指针被称为isa指针,它使对象能够访问类,通过类可以访问它继承的所有的类。

注意:虽然不是严格意义上语言的一部分,isa指针需要一个对象运行在OC运行时系统。一个对象需要等效的objc_object结构体无论是定义在这个结构的任意字段。然而,你很少甚至从来不需要创建你自己的根对象,继承自NSObject 或者 NSProxy的对象自动拥有可变的isa指针。

这些类的元素和结构如下图:

20160905164048696.png

当一个消息传递给一个对象的时候,消息函数沿着这个对象的isa指针在调度表找到它建立起方法选择器的类结构。如果它不能在这里发现选择器,obic_msgSend根据指针找到它的父类,在父类的调度表中寻找选择器。连续失败导致objc_msgSend沿着类继承结构直到寻找到NSObject类。一旦确定选择器的位置,函数调用表中的方法并且把它传给接收对象的数据结构。

这就是运行时方法选择实现的选择方法,在面向对象的编程术语中我们可以说方法和消息是动态绑定的。

为了加速消息传递过程,在方法被使用时,运行时系统缓存了方法的选择器和地址。每个类都有一个单独的缓存,它包含了继承的方法和自己类中定义的方法的选择器。在查找调度表之前,消息例行程序首先会在接收者对象的类的缓存中查找。(理论上来说,用过一次的方法很可能再次被使用)如果方法选择器在缓存里面,消息传递只会比函数调用慢一点。如果一个程序运行的足够长的事件来“热身”缓存,几乎所有的他发送的消息可以找到一个缓存的方法。当程序运行时,缓存根据新发送的消息动态增长。

2. 使用隐藏参数

当objc_msgSend找到一个方法的实现程序,它调用这个程序,传递消息中的所有参数。它也传递给程序两个隐藏参数:接收对象和方法选择器

这些参数给了每个方法实现关于调用它的两部分消息表达的明确信息,它们被说成隐藏的是因为它们在定义方法的源代码中没有声明。当代码被编译的时候它们被插入实现中。

虽然这些参数没有被显式声明,源代码仍然可以引用他们(就像它可以接收实例变量一样)一个方法引用接收对象作为自己,引用他自己的方法选择器作为cmd。在下面的实例中, cmd引用strange方法的选择器,自己作为strange消息的接收对象。

- strange
{
    id  target = getTheReceiver();
    SEL method = getTheMethod();

    if ( target == self || method == _cmd )
        return nil;
    return [target performSelector:method];
}

Self比两个参数更有用。事实上,这是接收对象的实例变量提供了方法的定义方式

3. 获取方法地址

为了避免动态绑定的唯一方法是得到一个方法的地址,当他是函数的时候直接调用。这可能是极少数的情况下是合适的,当一个特定的方法陆续执行了很多次,你想节省每次方法调用时的开销。

一个定义在NSObject中的方法,methodForSelector:,你可以要求一个指针指向它,然后通过指针来调用他。methodForSelector:这个指针必须返回正确的函数类型。同时返回值和参数的类型也应该包含在内。

参考地址:https://blog.csdn.net/coyote1994/article/details/52441513

上一篇下一篇

猜你喜欢

热点阅读