首页 » 漏洞 » Swift和Objective-C的运行时编程

Swift和Objective-C的运行时编程

 

本文要点

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

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

Cocoa基础设计模式就是一个典型的例子,其中包括了 Responder ChainNSUndoManagerKVC/KVO/BindingsCore 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将如何解决这些问题。

关于被访者

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 )关注我们。

原文链接:Swift和Objective-C的运行时编程,转载请注明来源!

0