Objective-C开发者眼中的Swift: 那些激动人心的新功能

本文翻译自 http://www.raywenderlich.com/73997/swift-language-highlightsjavascript

若是你和我同样,周一早上坐下来准备好好看看苹果的Keynote,兴奋地准备开始尝试一些新的API,结果你听到最多的是一门新的语言:Swift!你忽然被告知,这不是Objective-C的扩展,而是一门完彻底全新的语言。你是会激动呢,仍是高兴,抑或头脑一片空白?java

Swift将会彻底改变咱们写iOS和Mac应用的方式,在这片文章里,我归纳了这门语言的一些要点,并和Objective-C里面相应部分作了对比。python

注意:这不是Swift的入门读物,苹果已经发布了一本很全面的 Swift Programming Language,我强烈建议你先读它。这篇文章只会讨论一些特别cool、值得玩味的知识点。web

类型

第一个重大的事情是Swift提供了类型推断(type inference)。若是语言自己能够推断类型,那么开发者就不再必声明每个变量的类型了。编译器能够搞定这件事,以下面的例子,编译器能够自动把变量类型设为String:swift

// automatically inferred
var name1 = "Matt" // explicit typing (optional in this case) var name2: String = "Matt" 

与类型推断相伴而来的,Swift提供了类型安全的保证(type safety)。在Swift中,编译器(大部分状况如此,偶有例外)知道一个对象的完整类型,这让它能够作决定来怎么编译代码。数组

这和Objective-C是截然不同的,Objective-C是一门很是动态的语言,在编译期对象的类型并不彻底清楚,部分缘由是由于,你能够在运行时给既存的类型增长新的方法、增长一个完整的新类、甚至改变一个实例的类型。安全

让咱们看一个具体的例子,下面的Objective-C代码:数据结构

Person *matt = [[Person alloc] initWithName: @"Matt Galloway"]; [matt sayHello]; 

当编译器看到sayHello被调用的时候,它可以检查头文件里面是否有这个方法的声明,若是没有则编译会出错,可是它能作的仅此而已。这一般状况下是足够了,可以用来发现问题最早出现的地方,以及出现的打字错误。可是由于动态性,编译器不知道sayHello是否会在运行时被修改或者一直存在。譬如,它多是某个协议的一个可选方法(记住respondsToSelector: 检查)。app

由于不是强类型的,因此Objective-C编译器能作的就比较有限,没法在方法调用的时候进行一些优化。众所周知,方法调用都是经过objc_msgSend来动态dispatch的,你应该在backtrace里看到过。对这个函数而言,选择器的实现会通过查找和跳转两步,咱们不能否认这会增长额外开销和复杂度。函数

咱们再来看看Swift怎样完成一样的事情:

var matt = Person(name: "Matt Galloway"); matt.sayHello() 

在Swift中,编译器知道更多类型信息,它能确切地知道sayHello在哪里被定义。所以,它能作一些优化而直接跳转到方法的实现入口,而没必要经由动态dispatch。在其余语言中,可能会经过vtable来进行dispatch,这会比Objective-C采用的动态dispatch代价稍小一些,C++的虚函数就是这样作的。

在Swift中,编译器能提供更多帮助。他能阻止模糊类型带来的bug,也能经过编译优化让你的代码跑得更快。

泛型

另外一个重大的功能就是泛型。若是你对C++比较熟悉,那你可能以为泛型(generic)和模板(templates)比较像。由于Swift有严格的类型限制,你声明一个函数的时候,对于它的参数必须指明肯定的类型。但有时候你对于不一样的数据类型,可能都须要类似的功能。

一个例子是一般很是有用的数据结构Pair,你须要将一对值存放在一块儿。对于整数,你能够这么干:

struct IntPair {
    let a: Int! let b: Int! init(a: Int, b: Int) { self.a = a self.b = b } func equal() -> Bool { return a == b } } let intPair = IntPair(a: 5, b: 10) intPair.a // 5 intPair.b // 10 intPair.equal() // false 

看起来还比较有用。可是如今你想让它也能用在浮点数上,你可能就须要再定义一个FloatPair的类,这看起来就有点丑陋了。这时候泛型就能派上用场了,与再定义一个新的类相比,你也能够:

struct Pair {
    let a: T! let b: T! init(a: T, b: T) { self.a = a self.b = b } func equal() -> Bool { return a == b } } let pair = Pair(a: 5, b: 10) pair.a // 5 pair.b // 10 pair.equal() // false let floatPair = Pair(a: 3.14159, b: 2.0) floatPair.a // 3.14159 floatPair.b // 2.0 floatPair.equal() // false 

这就至关有用了!可能这还不够清楚说明你如今为何要使用这一功能,可是相信我:机会无限!你很快就会在你的代码中用到它了。

容器

你应该已经明白而且喜欢使用NSArray,NSDictionary以及他们的可修改版本了,如今你会看到他们在Swift中的等价物。幸运的是,他们很是相像,你能够这样来声明一个数组和字典:

let array = [1, 2, 3, 4] let dictinary = ["dog": 1, "elephant": 2] 

这对你来讲应该再熟悉不过,只是有些许差别:在Objective-C中,只要你愿意,数组和字典中能够放入任何类型的对象,可是在Swift里面,数字和字典必须指定数据类型,这都是经过前面所讲的泛型实现的。

上面的两个变量也能够这样定义,带上类型信息(记住:尽管你没必要这么作):

let array: Array = [1, 2, 3, 4] let dictinary: Dictionary = ["dog": 1, "elephant": 2] 

注意看在定义哪些类型能够放入容器时,泛型的使用。对于数组还有一个简略的形式,看起来可读性更好,但本质上是作一样的事情:

let array: Int[] = [1, 2, 3, 4] 

注意:如今会禁止任何Int以外的实例加入这个数组。这听起来不那么方便,可是毋庸置疑地有用:你的API不再用长篇累牍地解释它返回的数组中会保存什么内容,或者一个属性里面可以保存什么内容,你能够把这些问题交给编译器,由它来进行前期的错误检查和优化,是更明智的作法。

可变性

Swift中的容器的一个有意思的地方就是它的可变性。与Objective-C不同,ArrayDictionary并不存在一个可变版本,你只能经过letvar来区分它们。对于那些尚未读过原书,或者还未深刻Swift的读者(我建议大家读一下,尽快!),只须要知道let用来声明常量,var用来声明变量。let有点相似于C/C++/Objective-C中的const
let声明的容器没法改变它的大小,也就是说不能调用追加和删除方法,若是你这样作,就会获得相似错误:

let array = [1, 2, 3] array.append(4) // error: immutable value of type 'Array' only has mutating members named 'append' 

这一样适用于字典类。这致使编译器能够推导集合的性质并作适当优化。好比,若是大小不能改变,那么已经保存的值就永远不用考虑从新分配以接纳新值。因此,对于不会发生变化的集合对象,老是使用let来声明是一种很好地作法。

字符串

Objective-C中的字符串是众所周知的难用,就算只是链接字符串,代码也是很是冗长,例如:

Person *person = ...;
 
NSMutableString *description = [[NSMutableString alloc] init]; [description appendFormat:@"%@ is %i years old.", person.name, person.age]; if (person.employer) { [description appendFormat:@" They work for %@.", person.employer]; } else { [description appendString:@" They are unemployed."]; } 

这种代码太啰嗦了,里面包含了太多与目的无关的字符。一样的事情Swift里面作起来就是这样:

var description = "" description += "\(person.name) is \(person.age) years old." if person.employer { description += " They work for \(person.employer)." } else { description += " They are unemployed." } 

清晰许多!注意格式化一个字符串的方法,你如今也能够经过+=操做符来链接简单链接两个字符串,这都简洁太多,而且,也没有可变、不可变两种字符串存在。

Swift另外一个很是好的加强是字符串比较。你应该知道,Objective-C中使用==操做符并不能比较两个字符串是否相等,你必须使用isEqualToString:这个方法,这是由于==只是比较两个指针是否相等。Swift抛弃了这些概念,让你直接经过==操做符来直接比较内容是否相等,这也意味着字符串也能够用到switch语句中,下一节会介绍更多相关内容。

最后一则好消息就是Swift原生支持全部Unicode字符,你能够在字符串中使用任何Unicode代码点,甚至在函数名和变量名中,若是你愿意,你能够给一个函数命名为(pile of poo!)。

对Unicode支持的另外一个很是方便的地方,就是提供了内建方法,来计算一个字符串的真实长度。一旦支持全范围的Unicode字符,字符串长度的计算就再也不是一件简单的事情。在UTF8字符串里,你不能把字节数当成字符串长度,由于有些字符会占用超过一个字节的空间。在Objective-C中,NSString老是按照UTF16来计算长度,把每两个字节当成一个字符,但技术上说这并不老是正确的,由于有些Unicode字符会占用2个以上的字节。

幸运的是,Swift提供了一个很是方便的函数来计算代码点的真实个数,这就是名叫countElements的顶级函数。你能够像这样来使用它:

var poos = "a9" countElements(poos) // 2 

但这也并非对全部状况都成立,它仅仅只统计了一下Unicode代码点的个数,并无考虑引发字符变化的特殊情形。例如,你能够加一个元音符号到一个前缀字符上,这时候countElements会认为是两个字符,而实际上看起来只有一个字符:

var eUmlaut = "e\u0308" // ë countElements(eUmlaut) // 2 

说一千道一万,我想你确定会同意,相比Objective-C,Swift中的字符串好用太多!

Switch语句

我想讲的最后一件事情就是switch语句,与Objective-C相比,Swift对它作了完全的改进。这是一件颇有意思的事情,由于涉及到一些东西,若是不从根本上打破Objective-C是C的严格超集这一事实,就没法加入到Objective-C中去。

第一个让人兴奋的点是switch能够用到字符串上了,这多是你之前想作但作不到的。在Objective-C中,你只能经过大量的if语句和isEqualToString组合来达到相似效果,例如:

if ([person.name isEqualToString:@"Matt Galloway"]) { NSLog(@"Author of an interesting Swift article"); } else if ([person.name isEqualToString:@"Ray Wenderlich"]) { NSLog(@"Has a great website"); } else if ([person.name isEqualToString:@"Tim Cook"]) { NSLog(@"CEO of Apple Inc."); } else { NSLog(@"Someone else); } 

这样的代码可读性太差,而且有太多的字符要敲入。而一样的事情,在Swift中则:

switch person.name {
  case "Matt Galloway": println("Author of an interesting Swift article") case "Ray Wenderlich": println("Has a great website") case "Tim Cook": println("CEO of Apple Inc.") default: println("Someone else") } 

除了字符串上的switch以外,还有一些有意思的地方须要注意。这里没有任何break,这是由于case并不会一直执行下去,碰到下一个case的时候自动就退出了,这样能够减小好多不当心引起的Bug!

下面的switch语句可能会让你头脑发蒙,请作好准备:

switch i {
case 0, 1, 2: println("Small") case 3...7: println("Medium") case 8..10: println("Large") case _ where i % 2 == 0: println("Even") case _ where i % 2 == 1: println("Odd") default: break } 

首先,这里出现了一个break语句。这是由于switch须要彻底穷举,好比须要处理全部分支。在这里,咱们默认是什么都不作,因此使用break来明确声明这一意图。

其次,这里有.....,这两个用来定义范围的操做符。前一个表示闭区间(包含最右边的边界值),然后一个则表示开区间(不包含最右边的边界值),这将会带来难以置信的便利。

最后,能够直接把case的分支变量做为计算的输入。以上为例,若是一个数字不在0-10范围内,那么对于偶数会打印“Even”,而对于奇数则会打印出“Odd”。神奇到爆,有没有!

接下来

但愿这篇文章能让你初尝Swift的滋味,也能让你明白后面蕴藏了多少宝藏,可是还有更多事情要作,我强烈同意你去读一下苹果公司的书,看看其余文档,以帮助你来深刻学习这门语言——你早晚须要这么作!

相关文章
相关标签/搜索