改用swift来思考

改用swift来思考

现有代码库 + 你的头脑 + Swift。 怎么会错呢?

原文链接: Switching Your Brain to Swift

  • 原文日期: 2015/08/17
  • 译文日期: 2015/09/04
  • 译者:ray16897188

本文原基于360iDev 2015的一次谈话。等有视频之后我会贴上连接。与此同时,何不看看我在elsewhere上其他的Swift文章,以及我Twitter的主页呢?

完美的情形当然是从零开始的一个100% Swift的项目。如果你能这么做,很棒!但对与我们中的那些手里已经有现成代码库,而且有点儿想试试Swift的人来说,我们能从哪里开始?

为什么?

退一步:你为什么想用Swift去写代码呢?很多原因:Swift是新星;Swift有更好的语法(开始口水战了);Swift是Apple指引给我们的新方向。

将来,Swift的绝伦程度还会继续增加,并会成为大众首选的,有最完善的技术支持的,写OSX和iOS代码最简便的一种语言。

Swift是未来之路,就这么明了。那怎么做才能开始用Swift的方式去思考呢?

Swift的方式

有太多要考虑的事情了,但我们从两个大的方面说起:安全性和值语义(value semantics)。

安全性

Objective-C里的Nil棒极了。你可以给那个东西一直发消息, 好似明天是世界末日,运行时就会一直提供回应。

然而Swift中的Nil却是另一个很不同的怪物。通常来讲类型系统(type system)会把你从试图调用空函数或者访问空属性中挽救出来,阻止你这么干。但你能绕过类型系统,而这么做和在C语言中解引用一个空指针一样糟糕:在运行时你会被友善的捕获,然后你的app会崩溃。

Swift中一切都关乎类型安全。一个String是一个String,那它就是一个String。此时根本就没nil什么事儿。多想想C++中的引用而非C中的指针,因为引用永远也不会是nil。

可选类型

有了可选类型,nil就又回来了。一个可选类型的String可以确实是一个String,或者是nil。你必须做检查,每一次都要。

或者不做检查:你可以强制将这个可选类型变量拆包。或者将它换成一个隐式解析可选类型(implicitly unwrapped optional),这意味着它有些复杂的劣势,但可以当非可选类型一样去使用 - 直到它是nil的时候,app就崩溃了。

(所有的都拆包!)
(所有的都拆包!)

Cocoa里有太多可选类型,这意味着你每用其中一个,就得去检查里面是什么。

这就是一个大的思维转变。其中的思路是:你不应该留下任何偶然因素让发送的消息为nil。你应该知道,一个强类型系统中,某种东西要么是nil,要么就有值。

如果在运行时有不确定性,就做检查。不要强制去拆包。

把可选类型想成是一个盒子:这个盒子要么没有值(nil),要么就有值。但是你在强制拆包它之前总要先去检查一下。

(你得问:可选类型的盒子里面是啥?)
(你得问:可选类型的盒子里面是啥?)

还有很多其他展示Swift安全性的例子:构造器,更少量的未定义行为(less undefined behavior),内存安全。Nil的安全性是总会出现的一种。

值类型

值类型在Swift中随处可见。没什么新东西 - Objective-C就有像NSInteger和类似CGRect的结构体这样的基元(primitives)。但其绝大多数 - NSString,NSArray等等 - 都是类(classes),因此是引用类型。

Swift中则完全相反,如果你扫一眼头文件的话会发现标准库中有80多个结构体,只有4个类。

String,numbers和集合类型在Swift中都是值类型。这意味着如果你有一个可变的Swift String(是结构体)并把它传给一个函数,你获得的是一个拷贝(copy)。重复一遍,这并非一个吓人的新概念:我们在Objective-C中已经做了很久的copy和mutableCopy。这里大的转变是对于多数常见类型来说这是新的缺省行为。

不幸的是,如果你在Swift中用结构体写了些很棒的代码,在Objective-C中你是不能回访它们的。这就把我们带到了下一个话题:桥接。

桥接

Swift理所当然的被设计成了能与Objective-C良好协同工作。这基本上是一个必要的,无需争论的事实,因为Cocoa是为Objective-C而建立。所有这些Cocoa的API必须能够从Swift调用,这意味着你自己的Objective-C的类也可以良好的桥接到Swift中。

有个问题:从Swift开始,加入Objective-C代码,然后想在Objective-C中调用你的Swift代码。

从Swift到Objective-C

有太多的Swift特性完全不能桥接到Objective-C里,比如Swift自己的结构体和增强的枚举类型。这意味着如果你用Swift里所有最酷的特性写出来的最新最强的framework,其中绝大多数并不能被Objective-C访问到。

即使你给自己做限制,仅用Swift中那些可兼容Objective-C的特性去写,你也不能从Objective-C的类派生出一个用Swift写的子类。你可以遵循table View或collection view的模式,并用delegate和布局对象(layout objects)来规避这个问题,但是如果你的API是要被继承从而派生子类的话,还是要时刻记住这一点。

Swift中任何东西在Objective-C中都默认不可见。
如果你把你的class和protocol标记了@objc,那它们就可以在Objective-C中用了。动态调节器(dynamic modifier)也暗含着@objc的标注,让被标记的东西在Objective-C中可用,但它使那些被标记了dynamic的属性或者方法采用的是Objective-C的动态分发(dynamic dispatch)。

(Swift&Objective-C)
(Swift&Objective-C)

如果你想用swizzle之类的东西,你就需要用dynamic;仅仅给它标识@objc还不足以保证objc_msgSend()能被使用,因为方法是有可能被去虚拟化(devirtualized)或者被内联了。

重复一下,这仅仅对那些可兼容的特性有效。你自己写的Swift枚举类型中的方法并不适用。如果你枚举类型不是由Int型所构建,那就兼容不了。

Objective-C 到 Swift: 可空性(Nullability)

从Objective-C转到Swift有很多好消息。为了促进这个转换过程,在Objective-C中你要给你的属性,参数和返回值类型加上标注(annotation)。

  • _Null_unspecified (default) – 桥接到一个Swift隐式解析可选类型(implicitly-unwrapped optional)。
  • _Nonnull – 该值不可以是nil;桥接到一个常规的引用。
  • _Nullable – 该值可以是nil;桥接到一个可选类型。

如果你给你的Objective-C加了标注,你就会顺利的将类型桥接到Swift中。即使你没碰过Swift,你用Objective-C的时候这些标注也会在代码补全后出现。如果你告诉编译器一个方法的参数是_Nonnull的,然后传进去一个nil,你就会得到一个合理的编译器警告。

开始为代码加标注是很好的做法。使用现有的API的时候标注会帮到你,你开始用Swift的时候也会让你加快速度。

Objective-C 到 Swift: 轻量泛型(Lightweight Generics)

轻量泛型是Swift 2的新特性。集合类型NSArray,NSDictionary(对值类型)和NSSet可以包含任何旧的NSObject类型。

这意味着大量的类型转换(casting)。Objective-C中没有这个问题,但是记住:Swift是关乎安全性的。正确的转换涉及到大量的检查。你不应该强制的做类型转换,而应该先测试。

现在有了泛型,意味着你可以在Objective-C中这样写:

NSArray<NSString *> * _Nonnull

这是一个会包含NSString对象的NSArray。有了可空(nullability)的注释,你能看见它还说了这个array不会为nil,你总会得到一个array。如果你了解Java或者C++就会熟悉此处的泛型语法。
然后桥接到Swift就会是这样:

[String]

一个非常清晰的Swift String数组。
小的补充说明:轻量泛型只能应用于基本集合类:arrays,dictionaries和sets。

在浑水上架桥

我会建议从零开始新建一个Swift的项目 - 100%完全的Swift。如果你有第三方的frameworks,它们是Swift写的还是Objective-C写的就不会有太多影响 - 你能调用其中的任何一种。
如果你有一个现有的代码库,而且你想要开始引入Swift:那就尝试将桥接按照从Objective-C到Swift的方向进行。比如view controllers和views的具体实例在Swift中就运行的很好;它们继承自NSObject,如果需要的话你是可以从Object-C中访问到它们的。

(金门大桥)
(金门大桥)

但是对所有其他的来说:纯Swift泛型,由非整型,嵌套类型,结构体构成的枚举类型等等 - 就需要等到你身处于一个100% Swift世界中的那一天了。别被落下,这一天会比你想象的来的更早。

在那天之前,接着写能够友善兼容Swift的Objective-C的代码并保持对Swift最新技术的认知吧。有太多的资源会帮助你完成过渡。

资源

推荐阅读更多精彩内容