什么是Objective-C runtime?
本文是我关于一位大神在 Alt Tech Talks: London上关于 Objective-C runtime的演讲总结,如果你对Objective-C runtime感兴趣的话,应该看看这篇文章,特别是文章中的链接,一定会受益匪浅。
简单来说,Objective-C runtime是一个实现Objective-C语言的C库。对象可以用C语言中的结构体表示,而方法(methods)可以用C函数实现。事实上,他们 差不多也是这么干了,另外再加上了一些额外的特性。这些结构体和函数被runtime函数封装后,Objective-C程序员可以在程序运行时创建,检 查,修改类,对象和它们的方法。
除了封装,Objective-C runtime库也负责找出方法的最终执行代码。当程序执行[object doSomething]时,不会直接找到方法并调用。相反,一条消息(message)会发送给对象(在这儿,我们通常叫它接收者)。runtime库 给次机会让对象根据消息决定该作出什么样的反应。Alan Kay反复强调消息传递(message-passing)是Smalltalk最重要的部分(Objective-C根据Smalltalk发展而来),而不是对象:
由于以前关于这个话题我创造了“对象”这个词,现在很多人都对这个概念趋之若鹜,这让我感到非常遗憾。
其实这里面更为重要的理念是“消息命令”(messaging),这才是Smalltalk的核心内容(现在尚有一些内容还没有全部完成)。日 语中有个简短的单词叫做“ma”,它用来表示两个物体之间的东西,在英语中和它最相近的单词也许是“interstitial”。制造一个庞大且可扩展系 统的关键是设计它各个模块之间的通信方式,而不是关注它的内部属性和行为。
实际上,在一篇介绍Smalltalk虚拟机的文章里,这门编程技术被叫做消息传递或者消息传送范式。“面向对象”通常用来描述内存管理系统。
在演讲和文章中都使用ObjC runtime这个词,看似只有一个,实际上存在很多runtime库。虽然它们都支持对象的自省检查和消息接收,但是它们却有不同的特性和实现方式(例 如,同样是发送消息,Apple的runtime用一步完成,而GNU runtime会先查询这些消息,然后执行查找到的函数分两步完成)。以下所有的讨论,都是基于Apple的最新runtime库(苹果公司在OSX 10.5和iOS发布时的版本)。
在那次演讲中,我决定研究runtime库某些领域的功能。我找了一些希望更透彻了解的东西,然后把它们做成问答的形式组成我的演讲。
动态创建类
如何实现Key-Value Observing?
当我在准备这次演讲时,一篇叫做KVO considered harmful 的文章开始拥有很多拥趸。它提出了很多对KVO正确的批评,但相对于舍弃观察者模式不用,我更想探索出一种新的实现方式。
KVO实现观察者模式的关键是它偷偷摸摸将被观察对象的类改变了,它子类化原来的类后,就能够自定义该对象的方法来调用KVO的回调方法。这些都是通过 objc_duplicateClass这个方法完成,但很遗憾,这个方法并不公开,我们无法私自调用。
条条大路通罗马,好在除了objc_duplicateClass,还有其他方法可以通过使用秘密子类化的方式实现观察者模式,比如创建和注册 “class pair”。那么什么是class pair呢?对于Objective-C的类来说,都有一对Class的对象来定义它:Class对象定义了这个类的实例方法,而metaclass定义 了这个类的类方法。所以每个class其实是它metaclass的单例。
这个代码展 示了观察者模式的工作原理。当你给对象增加观察者时,这个对象首先会检查自己是否可被观察,如果是,它会新创建一个类,用我们自己的-dealloc替代 原来类的方法,同样它也会把-class方法替换掉,类似于KVO被观察对象,当你访问被观察对象的类名时,返回的是它原来的类名,而不是新生成的类。
创建完类后,我们需要照着 Key-Value Coding为属性增加一个setter方法:这个setter方法会获取这个属性修改前的值和修改后的值,然后调用block形式的回调函数,将这两个值告诉观察者。代码中根据我们的意愿,这个block可以异步调用。
请注意, -addObserverForKey:withBlock:会使用s object_setClass() 将被观察对象的类替代为新组建的类。这样做最主要的目的是将消息转变为方法的方式改变,但是这需要非常小心,原来的类和新的类必须有相同的成员变量布局。 因为成员变量也是用过runtime访问,修改某个对象的类可能导致runtime无法找到对应的变量。
我们在存储观察者集合时遇到些麻烦,因为没地方去存它们。给ObserverPattern这个类增加成员变量不起作用,因为根本没有生成这个类的对象。被观察对象的成员变量是它原来类的,它并没有考虑过这些观察者。
Objective-C runtime通过引入 associated objects 帮助我们摆脱这个困境。在runtime里,理论上所有对象都可以拥有包含其他对象的字典。通过associated references,被观察对象可以存储和访问他们的观察者,而不需要额外的成员变量。
如果你运行多次后,你会发现ObserverPattern 还是有点小毛病的。由于观察者回调是异步调用的,观察者接
收到的变化事件也是乱序的。这意味着观察者其实无法区分被观察属性的最终状态是什么,回调中的新值可能早已被修改。我这样做的目的是为了说明在KVO中同步调用回调其实是个有用的特色,并非bug。
欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) | 黑马程序员IT技术论坛 X3.2 |