Swift 和 Objective-C 的运行时编程

阅读数:1406 2017 年 7 月 10 日 17:49

本文要点

  • 运行时编程是 Objective-C 编程人员的重要工具,它提供了一些系统框架的基础。
  • 尽管运行时编程的确移除了不少的样板文本(Boilerplate),使开发人员可以编写更为精简的程序,但它是一把双刃剑,可能会导致软件缺陷难以被发现。
  • Swift 早就提供了强大的工具去解决一些在 Objective-C 中要使用运行时编程的问题,这些工具体现为不同的方法,例如闭包、泛型和协议等。
  • Swift 核心团队早已着手为 Swift 添加强大的动态特性,该工作在 Swift 3 中就打下了基础。
  • Swift 社区正在努力提供强大程序库,方便 Swift 开发人员静态地解决在 Objective-C 中被动态处理的问题。

几个月前,在 Objective-C/Swift 开发人员社区中开展了一场辩论。当时辩论的内容涉及Swift 中动态特性的缺失,以及纯Swift 开发环境所难以复制的运行时编程特性在Objective-C 中的重要地位。

Cocoa 基础设计模式就是一个典型的例子,其中包括了 Responder Chain NSUndoManager KVC/KVO/Bindings Core Data 等。为达到易于编程和高度解耦,这些设计模式深度借鉴了 Objective-C 的动态特性。

辩论不仅局限于 Swift 是否是一种动态语言,其中关注的问题还涉及:对于运行时编程,看上去 Swift 并未提供任何可比的特性;以及看上去 Apple 从未讨论过 Swift 中需要或值得具备哪些特性,以使用 Objective-C 的动态性可解决的问题在 Swift 中同样可以得到解决。

为获取更多的深层信息,InfoQ 访谈了 Chris Eidhof 和 Drew Crawford。Chris Eidhof 是《Functional Swift and Advanced Swift》一书的作者。Drew Crawford 是 Deckset 和 Scenery 的创立者,也是一名 Swift 开发人员,是首个静态链接 Swift/Linux 程序的开发者。

InfoQ:你们能介绍一下动态特性在 Objective-C 和 Cocoa 编程中的重要性吗?

Chris Eidhof:动态编程是一个被过度使用的词,即便仅限于 Objective-C 和 Cocoa 环境。对于部分开发人员,它意味着程序在运行中显示出了动态的行为。而对于另一部分开发人员,它可以指代对 KVO/KVC 的使用,或是迟绑定(Late binding)、子类(Subclassing)、Swizzling、运行时转化(Runtime casting)、动态类型(Dynamic typing)等。基于不同的交谈对象,动态编程还可能是上述技术的一个扩展子集。

在 Cocoa 中论及“动态编程”时,通常是指代运行时编程。在 Objective-C 中,使用运行时已成为大多数框架的重要基础。使用运行时编程我们可以编写更精简的程序,移除大量的样板文本。虽然代码及样板文本的精简是件好事,但是这也具有相应的风险,即在依赖于语言的特性时很难静态地发现缺陷。缺陷只有在运行时才会被发现。

不同于 Cocoa,UIKit 早已抛弃了运行时编程。绑定不再存在,并且与 Cocoa 相比,KVO/KVC 也不再重要了。Swift 在这个方向走得更远一些。

Drew Crawford:动态特性在运行时改变程序的行为,例如鸭子类型(Duck-typing,转化相似命名的方法所实现的无关类型)、在运行时创建新类、Swizzling 方法(在运行时改变函数)、选择器(在运行时确定所调用的函数)等。

Cocoa 是围绕着诸如此类的特性而设计的,其基本原则之一是假定存在着这些特性。Cocoa 的“目标 - 行为”模式的关键是选择器,而字符串是 NSCoding 的关键所在。过去的数年中,这些动态模式已深深地融入到了 Cocoa 结构中的数百个细微之处。

InfoQ:Swift 在哪些方面存在短板?哪些问题易于用 Objective-C 解决而难以用 Swift 实现?

Chris Eidhof: Swift 中的“反射”特性(Reflection)仍然十分不成熟,部分运行时特性依然完全不可用。例如,在 Objective-C 中可以用“键值观察者”(Key-Value Observing ,KVO)发现对象属性。在 Swift 中,KVO 只能与 Objective-C 对象一起使用,而不能与结构一起使用。

Swift 中不少事情难以用面向运行时的方法实现。在舍弃这些运行时特性时,最难做到的是学会如何做另类思考。其实 Swift 可用更少的代码解决几乎所有的问题,我们需要的是另辟蹊径。不使用运行时编程,我们还可以使用闭包、泛型、协议等特性,用更短的代码、更安全的方式解决同样的问题。例如,在线视频教学 Swift Talk 的“ Networking ”一节展示了如何安全地使用泛型为 Web 服务提供一个十分动态的接口。

Drew Crawford: XCTest 就是一个的典型例子。它是 Xcode 的测试框架,通过运行时搜索所有以“test”开始的函数,发现你所编写的新测试。如果没有该特性,为让 XCTest 知道如何调用你的测试,必须要手工维护所有测试的列表。现实中列表中的测试很容易被遗漏,这样遗漏的测试就得不到运行,导致你认为已测试的代码事实上并未得到测试。

Swizzling 是应对软件提供商缺陷的常用方法(可能会一些人指出这种用法是不对的)。当闭源软件库存在问题时,项目往往会深陷其中。通过在运行时围绕问题构建定制的环境,你可以嵌入自己的代码去应对这样的问题。该做法的问题在于,在软件提供商做更新后,程序很容易被破坏,因此这种应对方法是一把双刃剑。

InfoQ:你们是否设想过一种更为动态的 Swift?是否能在语言或架构中添加一定程度上的动态?Swift 是否应该做到完全动态?

Chris Eidhof:动态分发和消息传递是早就可用的特性,它们借助了类或协议的使用。或许更多地支持对运行时编程会是十分有用的,但可能要在安全和性能上付出代价。就我个人而言,自 Swift 公开发布以来我就在全职地编写 Swift 代码,我至始至终地运用着运行时编程特性。

有别于依赖运行时编程,我采用了其它所有允许的动态行为特性,包括闭包、高阶函数、协议、泛型等。使用这些特性让我具备了动态的行为,但依然用编译器做静态检查。在我们所著的《 Advanced Swift 》一书中展示了所有这些在实践中用到的技术。

我在近期的一个博客帖子中介绍了我们如何替换运行时编程(正是它使得 Objective-C 动态)为函数(正是它使得 Swift 动态)。我通过仅仅使用函数就重新实现了NSSortDescriptor API

Drew Crawford: Swfit 已具有了不少的动态特性。iOS/macOS 环境自带完全可用的 Objective-C 运行时,你可将 Swift 代码成功地关联到运行时,在 Swift 中做动态发布、选择器和 Swizzling。

而最大的问题在于如何在其它的平台上实现同样的事情,例如 Linux 等这些不具有 Objective-C 传统的平台。我们打算让越来越多的程序可在各大平台间移植,而开发人员在写新代码时坚持这些可移植的技术是些很有压力的事。这个问题在很大程度上并未得到解决。

但是我认为必须率先考虑 Swift 存在的合理性。Swift 并非是具有现代语法的 Objective-C。如果是这样的话,那么 Swift 项目岂非轻易就可实现(该语言的发展历史中,在很多点上的确是这么做的)。如果你正致力于此,那么只能说你并不需要一种新的语言、新的运行时、新的编译器、新的基金会。

相反,Swift 做出了这样的一个结论,即 Objective-C 所走的路并非我们想要的。我们想要的语言并非来自于接下来十年中每一届 WWDC 大会中将会给出的增量改进,唯一的实现方法是去重新审视那些十分基础的想法,去大量地重写代码,去杀掉我们曾心爱的代码。

我的意思并非是要将动态特性作为众矢之的,但我的确认为应抛开“如果 Objective-C 做了什么那么 Swift 也应做到”的这种理念。喜欢 Objective-C 的开发人员就继续使用它吧!Swift 需要规划自身的发展蓝图。它需要了解历史,也需要从中学习,这意味着以 Swift 要以自己的方式做事。

InfoQ:实现更具动态特性的 Swift 的挑战有哪些?在不降低 Swift 安全性的条件下,如何解决这些挑战?

Chris Eidhof:我并不认为会丧失过多的特性,但我还是要指出两点。第一,为反射添加更多的支持将会是十分有帮助的,但这可能会成为双刃剑,它也易于导致编写不正确的代码。

第二,也是更为重要的一点,我切实地期待着协议中的条件符合特性,它将使开发人员更具有表达力。例如,数组一般不遵从 Equatable,原因在于这要求数组中的所有元素都满足 Equatable。以前该条件在语言中是无法表述的,但是现在 Swift 团队正优先考虑添加条件符合

条件符合还将允许开发人员做数据类型泛型编程(Datatype-generic Programming),这是一种已经被 Haskell 等语言使用的方法。数据类型泛型编程是一种类型安全的编程方法,执行开发人员数据结构上的操作(类似于运行时编程)。

Drew Crawford:性能和安全性是困扰 Objective-C 的两个主要问题。很多人并不清楚,这两个问题是由 Objective-C 的动态直接导致的。

在 Objective-C 这样高度动态的语言中,编程人员的权力大到异乎寻常。他可以打开核心系统库并置入自己的代码。可以钩到方法调用中的每一个组成环节,创建具有无穷个方法的对象,或是创建在对象生命周期中反复闪现的方法。这样的权利真是匪夷所思呀。

但是能力越大,责任也就越大。作为编程人员你所具有的权力越大,留给编译器的权力就相应地减少了。这时就像是给 Objective-C 编译器戴上遮眼罩,将你的代码“看成”类似于在数组上的一个基本 for 循环。但是我们如何才能知道你并未将 NSArray 替换为具有无穷个方法的另一些对象?我们怎么能知道数组并非程序生成的并具有不限量元素?这些问题听上去十分疯狂,但是在 Objective-C 中是“合法上路”的,无法避免会有编程人员这么做。除非编译器屈从于你所编写的代码,否则它几乎无法在你的程序中前进一行,它正是按你代码的编写方式工作。而你编写的代码不可能总是很快的或是非常安全的。

Swift 收回了对编程人员具有无限权力的许可。在此交易中,我们得到了摘掉遮眼罩的编译器。它可以更深入地审视你的代码,通常会具有整个程序的鸟瞰图。这使它成功地实现了程度惊人的优化,并且可对远距离组件间交互等过程中的细微缺陷进行报警。一旦所能做的不再仅限于语法和括号风格之类的事情,就更能吸引人们去使用 Swift 了。它决定了 Swift 语言的特性。

其中所存在的难题是,我们如何在支持 Objective-C 开发人员所需特性的同时,维持 Swift 开发人员所期望的速度和安全性。事实上就我们自身而言是完全办不到的,但是如果群策群力,我们就能接近目标的达成。

InfoQ:为使 Swift 更适合于解决上述的关注问题,你们是否了解 Swift 开发中已经做了或是正在做哪些工作?

Chris Eidhof:是的,几乎所有讨论都是发生于邮件列表中的,Github 的 Swift-evolution 代码库也是一个非常好的资源。如果觉得其中的内容过于庞杂,可以去关注一个称为“ Swift Weekly Brief ”的每周简报。

Drew Crawford: 举一个实例,就是在 Swift 3 中所引入的“_typeByName”。它对应于 Objective-C 中的“NSClassFromString”解决方案,允许动态构建类。

你可能会对以下划线为开始命名的 API 设计心存疑虑,害怕它们在设计时并未被完全地考虑清楚,将来可能会发生改变。

InfoQ:Swift 的目标在于具备处理广泛问题的能力,包括服务器端开发和系统编程。从整体上看,解决这些问题对于 Swift 的重要性如何?你们是否认为 Swift 开发团队在听取意见?

Chris Eidhof: Swift 团队确实正在很好地听取意见。该团队活跃于所有的 Swift 邮件列表中,并深入参与了社区。Swift 是一种雄心勃勃的语言,我认为 Swift 团队的确发现了雄心和实用主义间的最有效点。但是,听取意见并不意味着要把每个可能的特性添加到 Swift 中。我印象十分深刻的是,Swift 团队经常是后退一步,尽量去理解问题真正所在,而非只是实现“增加一个特性”。

Drew Crawford:除了动态问题,Swift 现在还面对着很多挑战,例如核心函数的语法和命名还非常不稳定、不具备 ABI 兼容性、泛型没有完成等。这些问题十分严重,就像一栋建筑正在燃烧。在我看来,事实上核心团队十分关注这些问题。

在得到实现动态特性的建议后,团队从来没有说过要“不做”。团队的答复是:动态问题是一个重要的问题,但是在被其它的问题所干扰的情况下,该问题远非在当下所能得到解决的。我与 Objective-C 开发人员感同身受,他们苦恼于没有更好的动态解决方案,必须又得年复一年地忍受该问题。但是我认为如果连续每年都有超过大半的标准库被重命名,他们会更为苦恼。

应注意到,Apple 所维护 Objective-C 代码多于世界上任何其它人。Core Data 自身所暗藏的解决动态问题的高招可能要多于其它所有的生态系统的总和。因此认为看上去 Apple 对动态问题一无所知是一种误导,Apple 比其它任何人都更好地了解该问题,包括动态的优点和代价。

但是我认为还应该去做些事情。Swift Way™就是首要以静态方式去考虑解决那些在 Objective-C 中被动态解决的问题,并在其它的方法不成功时引入一些动态特性。如果某些程序更适合用 Objective-C 表示,那么我们就需要与这些程序和平共处,因为我们中的很多人也十分清楚,同样会有不少程序更适合于用 Swift 表示。正是这些差异赋予了一种语言其品质和特性,我们需要允许各种语言去做最好的自己,找到适合语言自身的发展道路。

Swift 的发展道路依然是一个十分开放的问题。我很有信心 Swift 将会包括比任何现有语言还要多的动态特性。本书的故事依然在发展,鉴于社区正在完成第三章,我认为现在对这本书如何结尾做出定论还为时尚早。

结论

在本文中,我们探讨了一些关于 Swift 缺失动态特性的问题,这些问题是 Objective-C 编程人员所关注的。我们访谈的两位开发人员深入地参与了推进 Swift 语言及其软件库生态系统的过程,这将有助于我们理解问题的发生场景,以及最终 Swift 将如何解决这些问题。

关于被访者

Swift和Objective-C的运行时编程Drew Crawford是一位软件开发人员、作家和顾问。他编写了首个静态链接的 Swift/Linux 程序。除了编写 Swift 程序,他还在 Austin 运作了一家精品开发公司,为不同规模的企业编写 iOS 应用和服务器软件,并授权定制的 Swift 技术。他所著的《Why Mobile Web Apps are Slow》一书被广为阅读,撰写的文章也已被翻译为多种语言,并被指定为全球多所大学的移动开发教学中的指定读物。

Swift和Objective-C的运行时编程Chris Eidhof是在柏林的一位 Swift 开发人员。他创立了 objc.io 和一系列的会议,并是图书《Functional Swift》和《Advanced Swift》的作者,同时还是 Deckset 和 Scenery 的创立者。在丰富的业余时间中,他喜欢跑步。

查看英文原文: Swift and Objective-C Runtime Programming


感谢冬雨对本文的审校。

给 InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

评论

发布