OC 运行时(Runtime)浅析(1)

前言:

2017年的工作已经开始一段时间了,看了一阶段机会;面试是一个过程,一段经历,边面试边学习吧;

作为比较重要的一部分理论,OC的运行时机制,显得比较突出,本文内容源自《Objective-C 2.0 运行时系统编程指南》,下述的内容是我读的时候做的笔记,小伙伴们可以看看这本书(很薄的)。


概述:

OC语言决定尽可能的从编译和链接时推迟到运行时。只要有可能,OC总会使用的动态的方式来解决。这意味着OC语言不仅需要一个编译器,同时也需要一个运行时系统来执行编译好的代码。

这儿的运行时系统扮演的角色类似于OC语言的操作系统,OC基于该系统来工作。

通常,并不需要知道和理解OC运行时系统和底层细节,但若熟悉之后,就会了解OC运行时系统的原理,并能更好的利用OC的优点。


主要内容:

1.NSObject类以及OC程序是如何和运行时系统交互的;

2.怎样在运行时动态的加载新类和消息转发给其他对象的范例;

3.怎样在程序运行时获取对象信息的方法;

接下来我们开始详细内容;


Objective-C程序三种途径和运行时系统交互:

1.通过Objective-C源代码;

2.通过Foundation框架中类NSObject的方法;

3.通过直接调用运行时系统的函数;


第一种:通过Objective-C源代码:

当您编译Objective-C类和方法时,编译器为实现语言动态特性将自动创建一些数据结构和函数;运行时系统的主要作用就是根据源代码中的表达式发送消息。

第二种:通过Foundation框架中类NSObject的方法;

某些NSObject的方法只是简单地从运行时系统中获取信息,从而允许对象进行一定程度上的自我检查。

例如:

class 返回对象的类;

isKindOfClass:和isMemberOfClass:则检查对象是否在指定的类继承体系中;

respondsToSelector:检查对象能否相应指定的消息;

conformsToProtocal:检查对象是否实现了指定协议类的方法;

methodForSelector:则返回指定方法实现的地址。

第三种:通过直接调用运行时系统的函数

运行时系统是一个有公开接口的动态库,由一些数据结构和函数的集合组成,这些数据结构和函数的声明头文件在/usr/include/objc中。

这些函数支持用纯C的函数来实现和OC同样的功能。还有一些函数构成了NSObject类方法的基础。

这些函数使得访问运行时系统接口和提供开发工具成为可能。尽管大部分情况下它们在OC程序不是必须的,但是有些时候对于OC程序来说某些函数是非常有用的。


消息(objc_msgSend):

本节描述代码的消息表达式,如何转换为对objc_msgSend函数的调用;

如何通过一个名字来指定一个方法,以及如何使用objc_msgSend函数;

1.获得方法地址;

2.objc_msgSend函数;

3.使用隐藏的参数;


第一个:获得方法地址:

利用NSObject类中的methodForSelector:方法,您可以获得一个指向方法实现的指针,并可以使用该指针直接调用方法实现。

methodForSelector:返回的指针和赋值的变量类型必须完全一致,包括方法的参数类型和返回值类型都在类型识别的考虑范围中;

第二个:objc_msgSend函数:

消息机制的关键在于编译器为类和对象生成的结构,每个类的结构中至少包括两个基本要素:

1)指向父类的指针;

2)类的方法表;

当新的对象被创建时,其内存同时被分配,实例变量也同时被初始化,对象的第一个实例变量也是一个指向该对象的类结构的指针,叫做isa。

通过该指针,对象可以访问它对应的类以及相应的父类;

当对象收到消息时,消息函数首先根据该对象的isa指针找到该对象所对应的类的方法表,并从表中寻找该消息对应的方法选标。如果找不到,objc_msgSend将继续从父类中寻找,知道NSObject类,一旦找到了方法选标,objc_msgSend则以消息接收者对象为参考调用,调用该选标对应的方法实现。


这就是在运行时系统中选择方法实现的方式。在面向对象编程中,一般称作方法和消息动态绑定的过程。

为了加快消息的处理过程,运行时系统通常会将使用过的方法选标和方法实现的地址放入缓存。每个类都有一个独立的缓存,同时包括继承的方法和在类中定义的方法。消息函数会首先检查消息接收者对象对应的类缓存。如果缓存中已经有了需要的方法游标,则消息仅仅比函数调用慢一点点。如果程序运行了足够长的时间,几乎每个消息都能在缓存中找到方法实现。程序运行时,缓存也将随着新的消息的增加而增加。

第三个:使用隐藏的参数:

当objc——msgSend找到方法对应的实现时,它将直接调用该方法的实现,并将消息中所有的参数都传递给方法实现,同时,它还将传递两个隐藏的参数:

接收消息的对象;

方法选标;

这些参数帮助方法实现获得了消息表达式的信息。它们被认为是“隐藏”的是因为它们并没有在定义方法的源代码中声明,而是在代码编译时在插入方法的实现中的。

尽管这些参数没有被显示声明,但是源代码中仍然可以引用它们(就像可以引用消息接收者对象的实例变量一样)。在方法中可以通过self来引用消息接收者对象,通过选标_cmd

来引用方法本身。


动态方法解析:

怎样提供一个动态方法的实现;

1.动态方法解析;

2.动态加载;


第一,动态方法解析:

比如OC属性前的@dynamic修饰符,表示编译器须动态的生成该属性的方法;

你可以通过实现resolveInstanceMethod:和resolveClassMethod:来动态的实现给定选标的对象方法或者类方法;

@implementation MyClass
+(BOOL)resolveInstanceMethod:(SEL)aSEL{
if(aSEL == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class],aSEL,(IMP)dynamicMethodIMP,"v@:");
return YES;
}
return [super resolveInstanceMethod:aSEL];
}
@end

通常消息转发(见“消息转发”)和动态方法解析是互不相干的。在进入消息转发机制之前,respondsToSelector:和instanceRespondToSelector:会被首先调用。您可以在这两个方法中为传进来的选标提供一个IMP。如果您实现了resolveInstanceMethod:方法但是仍然希望正常的消息转发机制进行,您只需要返回NO就可以了。


第二,动态加载:

Objective-C程序可以在运行时链接和载入新的类和范畴类。新载入的类和在程序启动时载入的类并没有区别。动态加载可以用在很多地方。例如,系统配置中的模块就是被动态加载的。

在Cocoa环境中,动态加载一般被用来对应用程序进行定制。您的程序可以在运行时加载其他程序员编写的模块——和Interface Build载入定制的调色板以及系统配置程序载入定制的模块类似。这些模块通过您许可的方式拓展了您的程序,而你无需自己来定义或者实现。您提供了框架,而其他的程序员提供了实现。


消息转发:

通常,给一个对象发送它不能处理的消息会得到出错的提示,然而,OC运行时系统在抛出错误前,会给消息接受对象发送一条特别的消息来通知该对象。

1.消息转发;

2.消息转发和多重继承;

3.消息代理对象;

4.消息转发和类继承;


第一,消息转发:

如果一个对象收到一条无法处理的消息,运行时系统会在抛出错误前,给该对象发送一条forwardInvocation:消息,该消息的唯一参数是个NSInvocation类型的对象——该对象封装了原始的消息和消息的参数。

您可以实现forwardInvocation:方法来对不能处理的消息做一些默认的处理,也可以以其他的某种方式来避免错误被抛出。如forwardInvocation:的名字所示,他通常用来将消息转发给其他的对象。

forwardInvocation:消息给这个问题提供了一个更特别的,动态的解决方案:当一个对象由于没有相应的方法实现而无法响应某消息时,运行时系统将通过forwardInvocation:消息通知该对象。每个对象都从NSObject类中继承了forwardInvocation:方法。然而,NSObject中的方法实现只是简单地调用了doesNotRecognizeSelector:。通过实现您自己的forwardInvocation:方法,您可以在该方法实现中将消息转发给其他对象。


要转发消息给其他对象,forwardInvocation:方法所必须做的有:

1)决定将消息转发给谁,并且;

2)将消息和原来的参数一块转发出去;

消息可以通过invokeWithTarget:方法来转发;


forwardInvocation:方法也可以对不同的消息提供同样的响应,这一切都取决于方法的具体实现。该方法所提供是将不同的对象链接到消息链的能力。

注意:forwardInvocation:方法只有在消息接受对象中无法正常响应消息时才会被调用。所以,如果您希望您的对象将negotiate消息(一个方法)转发给其他对象,您的对象不能有negotiate方法。否则,forwardInvocation:方法将不会被调用。


第二,消息转发和多重继承:

消息转发很像继承,并且可以用来在OC程序中模拟多重继承;

一个对象通过转发来响应消息,看起来就像该对象从别的类那里借来了或者“继承”了方法实现一样;

转发消息的对象看起来有两个继承体系分之——自己的和响应消息的对象的,在上面的例子中,Warrior看起来同时继承自Diplomat和自己的父类。

消息转发提供了多重继承的很多特性。然而,两者有很大不同:多重继承是将不同的行为封装到单个的对象中,有可能导致庞大的、复杂的对象。而消息转发是将问题分解到更小的对象中,但是又以一种对消息发送对象来说完全透明的方式将这些对象联系起来。


第三,消息代理对象:

消息转发不仅和继承很像,他也使得以一个轻量级的对象(消息代理对象)代表更多的对象进行消息处理成为可能。

假设您有个对象需要操作大量的数据——他可能需要创建一个复杂的图片或者需要从磁盘上读取一个文件的内容。创建一个这样的对象是很费时的,您可能希望推迟它的创建时间——知道他真正需要时,或者系统资源空闲时。同时,您又希望至少有一个预留的对象和程序中的其他对象交互。在这种情况下,您可以为该对象创建一个轻量级的代理对象。该代理对象可以有一些自己的功能,例如相应数据查询消息,但是它主要的功能是代表某个对象,当时间来到时,将消息转发给被代表的对象。当代理对象的forwardInvocation:方法收到需要转发给被代理的对象的消息时,代理对象会保证所代表的对象已经存在,否则就创建他。所有发到被代表的对象的消息都要经过代理对象,对程序来说,代理对象和被代理对象是一样的。


第四,消息转发和类继承:

尽管消息转发很“像”继承,但他不是继承。例如在NSObject类中,方法respondsToSelector:和isKindOfClass:只会出现在继承链中。例如,如果想一个Warrior类的对象询问他能否响应negotiate消息,返回值是NO,尽管该对象能够接收和响应negotiate。大部分情况下,NO是正确的响应。但不是所有的时候都是的。例如,如果您使用消息转发来创建一个代理对象以拓展某个类的能力,这的消息转发必须和继承一样,尽可能的用户透明。如果您希望您的代理对象看起来就像是继承自它代表的对象一样,您需要重新实现respondsToSelector:和isKindOfClass:方法。

注意:消息转发是一个比较高级的技术,仅适用于没有其他更好的解决办法的情况。它并不是用来代替继承的。如果您必须使用该技术,请确定您已经完全掌握了转发消息的类和接受转发消息的类的行为。


关于 类型编码和属性声明 熟悉即可,这就不描述了,大家看原文即可。


总结:

可能很多时候会被人问道底层,其实多多理解编程语言的原理,再结合具体的底层知识才会更有益处。









评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值