Concepts 概念
我们直接来看 Objective-C 与 Swift 最显著的区别。我真的很想用代码来说明,但在此之前有一些你必须了解的事情,所以请多担待,我保证马上就有代码可以读啦!
了解 Objective-C 最重要的一点是:它是 C 语言的一个严格超集,C 是一个40多岁的语言。这意味着,有效的 C 语言代码,也是有效的 Objective-C 代码,你可以自由地混编。你甚至可以使用 C++ 与 Objective-C 混编,也就是我们所说的 Objective-C++,但这并不常见。
C 和 C++ 有很多传统的东西,C 和 Objective-C 汇集在一起的地方有点粗糙,但确实,在基于 Objective-C 的项目中,使用 C 和 C++ 的代码是非常容易的。我在 iOS 平台编写的第一个游戏是用 Objective-C++ 编写的,其中用了少量的 Objective-C 代码用来调用底层 C++ 代码。
第一个显而易见的传统是「头文件」:当你在 Objective-C 中创建一个类时,它由 YourClass.h(头文件)和 YourClass.m(实现文件)组成。 "m" 最初代表 「消息」,但现在我们认为它是 "iMplementation" 文件。你的「头文件」描述了暴露给外部世界的「类」:可以访问的「属性」和可以调用的「方法」。你的「实现文件」是你为这些方法编写实际代码的地方。
在 Swift 中,不是把「类」或「结构体」分割成 ".h" 和 ".m" 文件,而是在单个文件中创建。但在 Objective-C 中,分割两者的概念非常重要。当你想使用某一个类时,编译器只需要读取 ".h" 文件,就可以知道怎么使用这个类。这个机制保证了你使用的是封闭的源码组件,这一点类似Google的分析库:同样给了你一个 ".h" 的头文件,它描述了组件之间如何工作;以及 ".a" 文件,其中包含真正为实现某一功能所写的源码。
第二个明显的「历史遗留」是 C 语言的「预处理」。如果你对它的工作原理感兴趣,我将在本书后面一个独立的章节来阐明。但其实它的概念并不复杂:「预处理」是一个编译的阶段,这个阶段发生在 Objective-C 代码构建(build)之前,并且允许你在代码编译之前重写它。但在 Swift 里你不能这么做,因为预处理需要头文件和宏,宏命令可以实现在代码构建的时候完成替换,但 Swift 里并没有头文件。举个例子,你可以创建一个叫 PI 的宏,并赋予它一个值,而不是重复写3.14159265359。这个有点类似在 Swift 定义常量。宏命令可以做很多事情,使用它也有利有弊,更多关于「预处理」的内容,你可以查阅「预处理」那一章。
Easy differences 一些简单的区别
和 Objective-C 相比, Swift 是一门非常现代的语言。对比两者可以发现,Objective-C 不包含下面这些特性:
- 自动类型推断
- 运算符重载
- 协议扩展
- 字符串插值
- 命名空间
- 元组
- 可选值
- Playgrounds
- guard 和 defer 语法
- 闭区间和半开区间
- 带值枚举
Objective-C 中也有结构体(struct),但使用的频率比 Swift 中低很多,正如 Objective-C 的 "Objective"命名一样,它把更多的精力放在「对象」上。
Objective-C 与 Swift 在大多数运算符上是一致的。但是, Swift 2.2 中弃用了 "++" 和 "--" 运算符, nil 合并运算符由 "??" 变为 "?:"。
Namespaces 命名空间
乍一看 Objective-C 中没有「命名空间」这一点,没什么特别的,但这里要再解释一下这一点。所谓的「命名空间」是一种组合形式,它把离散的、可以重复使用的区块,组合成一个具有某种功能的整体。当你给了你的代码一个「命名空间」的时候,它就保证了你在创建对象时所使用的命名,和别人用的不会重叠。举个例子,你可以创建一个名为 Person 的类,不用担心和 Apple 创建另一个名为 Person 的类重叠。因为 Swift 会自动给你的代码一个「命名空间」,把你的类被自动包装在里面,类似 YourApp.YourClass 。
Objective-C 里没有「命名空间」的概念,这意味着,它要求所有的类名都是全局唯一的。这说起来容易做起来就难了。假设你使用了五个库,这些库可能每个都使用三个其他的库,那每个库可能定义许多类名。这可能是库 A,库 B,库 C,甚至包括了它们的各个不同版本。(译者注:这也是为什么 OC 的命名总是那么长……)
这就很麻烦了。苹果的解决方案是简单粗暴的:使用2-4个字母的前缀,使每个类名称独特。想想看:UITableView,SKSpriteNode,MKMapView,XCTestCase…… 你看,可能会因为一直在用这种前缀方法,你甚至都没有意识到它是在解决 Objective-C 没有「命名空间」这个缺点。
What, no optionals? 什么鬼,竟然没有可选值?
Objective-C 中没有「可选值」的概念,这也是有人欢喜有人愁。「可选值」导致了 Swift 首次发布时出现的各种问题:所有的Objective-C API 都是要导入到 Swift 中去的,这意味着,程序员必须要确定这里是 "UIView" 还是 "UIView?",换句话说,也就是必须想好这里「必须有一个视图」,还是「可以为空」。
在 Swift 中,这种区分真的很重要。假设你想使用的是一个值,但是实际上这里是为空(nil)的,那么你的程序就崩溃了。但是在Objective-C 中,使用 nil 值是完全可以的.你可以向 nil 对象发送一个消息,它只是不回应,什么都不会发生。
重要的事情要加粗:你可以发送消息到一个 nil 对象,什么都不会发生。 这在 Swift 中是绝对不行的,这样做就是一个程序员考虑不周产生的错误。但在 Objective-C 中不一样,如果你跑了代码,但什么都没有发生,你应该去检查一下,是不是给一个 nil 对象发送了消息。
为了更好地桥接 Objective-C 和 Swift,苹果引入了一些新的关键字,来填补 Objective-C 没有可选值的空白。然后它重审了自己的 API 来使用这些新的关键字,这就是为什么随着时间的推移,Swift 中的一些 API 改变了它们的可选性。这些都会在本书后面的 Nullability 章节中进行讨论。这些改动对 Objective-C 来说是一种完善,但很少有人用就是了。
Safety 安全性
由于没有可选值,并且你能够给 nil 对象发送消息,Objective-C 的安全性看起来比 Swift 要低。
然而,不仅是看起来,事实上 Objective-C 的安全性就是比 Swift 要低得多。Objective-C 允许你强制把一种数据类型转换成另一种数据类型,直到最近它才引入「泛型」的概念(例如,只能容纳字符串的数组)。在 Swift 中,我们能轻松地混合 ASCII码 和表情符号,但这样的高级字符串功能在 Objective-C 里是没有的。同时,它允许你读取不存在的数组值(译者注:也就是没有「数组越界」这一说)。在 Objective-C 中使用 switch 语法的时候,你也不需要穷举。并且几乎所有 Objective-C 中的属性都是变量,极少有人声明常量。
当你从 Swift 世界来到 Objective-C 世界的时候,这些特性看起来更像是错误,你会觉得 Objective-C 世界就像蛮荒之地一样。在某种程度上,确实是这个样子。如果你一直以来是 Swift 开发者,那 Objective-C 对你来说简直松散得不像话,你会疑惑苹果是如何用它建立起自己的整个生态系统的。但是换位想象一下,对于Objective-C 开发者来说,Swift 是什么样的呢?编译器就变得显得更加迂腐了,你需要把最后一个字母都拼出来,才能构建运行代码。 「对的,我确确实实要把一个 UInt 转换成 Int」这类完全没有必要在 Objective-C 存在的东西,在 Swift 世界里,这只是恼人的开始。
这里有一个小小的帮助,可以让 Objective-C 的危险性降低一点。当你创建 Objective-C 项目的时候,在 Xcode 中的 " Build Settings" 选项卡中,将 "Treat Warnings as Errors" 设置为 "Yes"。这个设置会阻止你犯一些可怕的错误,例如试图将一个数字强行转化成一个字符串。