您的位置:首页 > 移动开发 > Objective-C

详解Objective-C runtime

2016-04-19 15:14 483 查看
原文地址:http://blog.securemacprogramming.com/2013/12/by-your-_cmd/

感谢翻译小组成员
wingpan
热心翻译。本篇文章是我们每周推荐优秀国外的技术类文章的其中一篇。如果您有不错的原创或译文,欢迎提交给我们,更欢迎其他朋友加入我们的翻译小组(联系qq:2408167315)。

本文是我在
Alt Tech Talks: London
上关于
Objective-C


runtime
的演讲总结,如果你对
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


创建对象

那些额外的字节都是干啥用的?

当你创建一个
Objective-C
对象时,
runtime
会在实例变量存储区域后面再分配一点额外的空间。这么做的目的是什么呢?你可以获取这块空间起始指针(用
object_getIndexedIvars
),然后就可以索引实例变量(
ivars
)。好吧,下面我会使用自定义数组来说明一下索引
ivars
的用处。

让我们创建一个数组!从这个
SimpleArray
中可以看到两件事情:最明显的一件是它使用了类簇模式。当使用
+alloc
方法返回对象时,一般情况下已经为这个对象分配了所有的内存,但是在这个例子中,在
+alloc
时并不知道需要多大的内存空间。只有当调用了 -
initWithObjects:count:
以后,才能根据数组内对象数量计算出这个数组需要多大的内存,所以
+alloc
只是返回一个占位符,只有在初始化后才会分配和返回真正的数组对象。

或许你会问为什么我们要用类簇把事情搞那么复杂,使用
calloc()
另外分配一块大小合适的缓存,然后把那些对象指针存到里面不就得了?答案是希望利用局部性原理提高访问性能。从数组的设计上我们可以看出,每次数组指针被访问时,之后会有很大几率访问到缓存指针,所以把它们肩并肩的放入内存意味着找到其中一个就是找到了另外一个。

消息派发

消息如何转发?

Objective-C
其中一个强大特性是对象不需要实现某个方法,尽管它在编译时声明了该选择符(
selector
)。但它可以在运行时再决定方法实现,或者将这些消息转发给其他对象,或者发出异常,亦或做一些其他事情。但是这个特性的某些方面曾经一直困扰我:消息转发(
message
forwarding
)会调用
-forwardInvocation:
,然后传入一个
NSInvocation
对象。但是这个
NSInvocation
类是在
Foundation
库中定义的,难道说
runtime
工作需要
Foundation
配合?

我试着挖掘其中的原因,发现答案并不是我想的那样,
runtime
不需要知道
Foundation
runtime
会让程序定义转发函数(
forwarding function
),当
objc_msgSend()
无法找到该
selector
的实现时,那个转发函数就会被调用。程序一启动,
CoreFoundation
就将 -
forwardInvocation:
定义成转发函数。

让我们来创建一个
Ruby
!当然并不是真的实现完整的
Ruby
Ruby
有一个叫做
#method_missing
的函数,当对象收到一个它没有实现的消息时,这个函数就会被调到,这和
Smalltalk
的做法比较相似。使用
objc_setForwardHandler
,我们也能在
Objective-C
的类中实现类似
Ruby
methodMissing:
方法。

总结

Objective-C runtime
可以有效的帮助我们为程序增加很多动态的行为。一些开发者除了使用
method swizzling
帮助调试程序,并不会在实际程序中使用它,但
runtime
编程的确有很多功能,它应该成为实际应用代码编写的重要工具。

转载自:详解Objective-C runtime
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  objective-c runtime