详解Objective-C runtime
2016-04-19 15:14
483 查看
原文地址:http://blog.securemacprogramming.com/2013/12/by-your-_cmd/
感谢翻译小组成员
本文是我在
什么是Objective-C runtime?
简单来说,
除了封装,
由于以前关于这个话题我创造了“对象”这个词,现在很多人都对这个概念趋之若鹜,这让我感到非常遗憾。
其实这里面更为重要的理念是“消息命令”(
实际上,在一篇介绍
在演讲和文章中都使用
在那次演讲中,我决定研究
动态创建类
如何实现Key-Value Observing?
当我在准备这次演讲时,一篇叫做
条条大路通罗马,好在除了
这个代码展示了观察者模式的工作原理。当你给对象增加观察者时,这个对象首先会检查自己是否可被观察,如果是,它会新创建一个类,用我们自己的
创建完类后,我们需要照着
请注意,
我们在存储观察者集合时遇到些麻烦,因为没地方去存它们。给
如果你运行多次后,你会发现
收到的变化事件也是乱序的。这意味着观察者其实无法区分被观察属性的最终状态是什么,回调中的新值可能早已被修改。我这样做的目的是为了说明在
创建对象
那些额外的字节都是干啥用的?
当你创建一个
让我们创建一个数组!从这个
或许你会问为什么我们要用类簇把事情搞那么复杂,使用
消息派发
消息如何转发?
我试着挖掘其中的原因,发现答案并不是我想的那样,
让我们来创建一个
总结
转载自:详解Objective-C runtime
感谢翻译小组成员
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
相关文章推荐
- Java Runtime Environment 5.0 Update 12 下载
- php set_magic_quotes_runtime() 函数过时解决方法
- Asp.Net 程序错误Runtime Error原因与解决
- System 类 和 Runtime 类的常用用法介绍
- Objective-C的内省(Introspection)用法小结
- Objective-C中常用的结构体NSRange,NSPoint,NSSize(CGSize),NSRect实例分析
- Objective-C中使用NSString类操作字符串的方法小结
- Objective-C中NSNumber与NSDictionary的用法简介
- Objective-C中NSLog输出格式大全
- 全面解析Objective-C中的block代码块的使用
- Swift调用Objective-C编写的API实例
- Swift、Objective-C、Cocoa混合编程设置指南
- Objective-c代码如何移植为Swift代码 Objective-c代码转移到Swift过程介绍
- Swift调用Objective-C代码
- 以实例讲解Objective-C中的KVO与KVC机制
- 简介Objective-C解析XML与JSON数据格式的方法
- 浅析Objective-C中分类Category的使用
- 举例讲解Objective-C中@property属性的用法
- Objective-C编程中语句和变量的一些编写规范建议
- 理解Objective-C的变量以及面相对象的继承特性