详解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

时间: 2024-08-04 19:39:48

详解Objective-C runtime的相关文章

iOS开发——高级特性&Runtime运行时特性详解

Runtime运行时特性详解 本文详细整理了 Cocoa 的 Runtime 系统的知识,它使得 Objective-C 如虎添翼,具备了灵活的动态特性,使这门古老的语言焕发生机.主要内容如下: 引言 简介 与Runtime交互 Runtime术语 消息 动态方法解析 消息转发 健壮的实例变量(Non Fragile ivars) Objective-C Associated Objects Method Swizzling 总结 引言 曾经觉得Objc特别方便上手,面对着 Cocoa 中大量

【10.2.3】ArcGIS Runtime for Android搭建开发环境过程中问题详解

一.Visual Studio Ultimate2012安装过程问题 1.问题描述 安装完成后,您将看到一条消息,指示安装程序已完成,但并不是所有的功能具有已正确安装,以及以下警告消息: Microsoft Web Deploy 3.0 所需的证书不在有效期内根据当前系统时钟或签名文件中的时间戳验证时. 2.解决方案 修改电脑系统时间为2013年7月,断网后重新安装,成功后再联网. Visual Studio Ultimate2012激活密钥:RBCXF-CVBGR-382MK-DFHJ4-C6

objective C 内存管理及属性方法详解

oc为每个对象提供一个内部计数器,这个计数器跟踪对象的引用计数,当对象被创建或拷贝时,引用计数为1,每次保持对象时,调用retain接口,引用计数加1,如果不需要这个对象时调用release,引用计数减1,当对像的引用计数为0时,系统就会释放掉这块内存,释放对象调用dealloc 当对象包含其他对象时,就得在dealloc中自己释放他们 NSObject是IOS所有类的基类 有两个基本函数,alloc和dealloc alloc类似于C++的new,dealloc类似于delete 当对象的re

iOS开发:详解Objective-C runTime

Objective-C总Runtime的那点事儿(一)消息机制 最近在找工作,Objective-C中的Runtime是经常被问到的一个问题,几乎是面试大公司必问的一个问题.当然还有一些其他问题也几乎必问,例如:RunLoop,Block,内存管理等.其他的问题如果有机会我会在其他文章中介绍. 本篇文章主要介绍RunTime. RunTime简称运行时.就是系统在运行的时候的一些机制,其中最主要的是消息机制.对于C语言,函数的调用在编译的时候会决定调用哪个函数( C语言的函数调用请看这里 ).编

Objective - c Foundation 框架详解2

Objective - c  Foundation 框架详解2 Collection Agency Cocoa provides a number of collection classes such as NSArray and NSDictionary whose instances exist just to hold onto other objects. cocoa 提供了一系列的集合类,例如,NSarray,NSdictionary.它们存在的目的就是为了保持其他对象. 1.1.1N

Runtime详解(下)

Runtime应用 1.Runtime 交换方法 应用场景:当第三方框架或者系统原生方法功能不能满足我们的时候,我们可以在保持系统原有功能的基础上,添加额外的功能. 需求:加载一张图片直接用系统的[UIImage imageNamed:@""]:是无法知道到底有没有加载成功.给系统的imageNamed添加额外功能,(是否加载图片成功,以及加载未完成的时候,用模糊的该照片代替) 方法一:继承系统的类,重写方法:(每次使用都需要导入) 方法二:使用runtime,交换方法 实现步骤: (

《招一个靠谱的移动开发》iOS面试题及详解(上篇)

多线程.特别是NSOperation 和 GCD 的内部原理. 运行时机制的原理和运用场景. SDWebImage的原理.实现机制.如何解决TableView卡的问题. block和代理的,通知的区别.block的用法需要注意些什么. strong,weak,retain,assign,copy nomatic 等的区别. 设计模式,mvc,单利,工厂,代理等的应用场景. 单利的写法.在单利中创建数组应该注意些什么. NSString 的时候用copy和strong的区别. 响应值链. NSTi

[深入浅出Cocoa]详解键值观察(KVO)及其实现机理

一,前言 Objective-C 中的键(key)-值(value)观察(KVO)并不是什么新鲜事物,它来源于设计模式中的观察者模式,其基本思想就是: 一个目标对象管理所有依赖于它的观察者对象,并在它自身的状态改变时主动通知观察者对象.这个主动通知通常是通过调用各观察者对象所提供的接口方法来实现的.观察者模式较完美地将目标对象与观察者对象解耦. 在 Objective-C 中有两种使用键值观察的方式:手动或自动,此外还支持注册依赖键(即一个键依赖于其他键,其他键的变化也会作用到该键).下面将一一

YII2框架详解

yii2框架的安装我们在之前文章中已经提到下面我们开始了解YII2框架 Yii2的应用结构: 目录篇: advance版本的特点是:根目录下预先分配了三个模块,分别是前台.后台.控制台模块. 1.backend 它主要用于管理后台,网站管理员来管理整个系统. assets 目录用于存放前端资源包PHP类. 这里不需要了解什么是前端资源包,只要大致知道是用于管理CSS.js等前端资源就可以了. config 用于存放本应用的配置文件,包含主配置文件 main.php 和全局参数配置文件 param