有趣的Swift特性

本文翻译自Mike Ash的博客:Interesting Swift Featureshtml

 

今天,我想要开始一些事情,探索几个Swift的特性,其从Objective-C的角度看是使人感兴趣的而且不寻常的,而且我将会思考他们会有什么好处。git

 

显式可变值程序员

Swift 使一些可变值做为一个优秀语言的构成,而且使任何可变类型像这样被标识。例如:github

var a: Int var b: Int?

在这里,a是一个显式的Int,而且它老是包含一些整型值。b是一个可变的Int,而且它也包含一个整型值,或者它什么也不包含。编程

 

那个例子并没有特别之处,可是借助于其余的类型它会变得更有意思。swift

var c: String var d: String?

像上面这样,c是一个简单的String,而且它老是包含一些字符串值。d 是一个可变的String,而且它也可能包含一个字符串值或者可能什么也不包括。api

 

让咱们看看Objective-C 中的等价的声明:数组

int e; NSString *f;

这里,e是一个明显的整型,而且它老是包含一些整型值。f 是一个NSString *,这就意味着它也能够包括一个指向NSString的指针,或者它可能包括nil,这就一般意味着"nothing"。闭包

 

注意:e是一个非可变类型,而f是有效的可变类型。NULL在c中的存在(和Objective-C中的nil同样)意味着语言中的所有的指针类型是隐式的可变类型。若是你有一个 NSString *,就意味着”指向 NSString 的指针,或者是nil”。若是你有一个char *,就意味着“指向char 的指针,或者NULL”.app

 

C中存在的NULL在成为指针时隔开整个静态系统。每个指针类型都是half truth类型的。不管什么时候你看到一个类型”指向X的指针”,那老是一个含蓄的表述”指向X的指针或者为NULL”。

 

NULL 行为彻底不一样。你能够解引用一个指针,可是你不能解引用NULL。Objective-C中你将给它发送消息,可是返回值老是0,甚至是没有意义的。

[nil isEqual: nil] // returns NO!

记录一个nil字符串,你会获得null。[nil arrayByAppendingArray: obj]不会产生一个包括单元素obj而不是nil的数组。全部都有两个不一样的行为:常规的一个,以及nil或者NULL值。

 

这是一个主要的问题由于咱们的代码不是那种方式运行。有时候,咱们写咱们的代码“指向X或者NULL的指针”,可是有时咱们仅仅写“指向X的指针”。可是没法在语言中表达这个字母类型。

 

为了说明这个问题,考虑以下两个问题:

1.nil 是一个传递给isEqual:的合法参数吗?

2.nil 是一个传递给isEqualToString:的合法参数吗?

 

我将会在你检查文档的时候给你一点时间。

 

第一个问题是“yes”。文档写道:“在这个方法返回NO的时候或许是nil。”

 

第二个问题是...no吗?可能吗?文档没说明。最好的假设若是nil不是明确被容许,那么它就不合法。

 

方法参数有时候能够为nil,有时候不行。返回值也是一样。一些方法从不返回nil,然而一些返回。若是一个方法返回nil而且你传递返回值给一个不接受nil的方法,问题就来了。你必须增长校验,而且编译器不会帮助你,就像编译器知道的那样,一切接受nil。甚至若是你能够掌控一切,文档常常掩盖nil处理。

 

这有另外一个要说明的问题:这个代码合法吗?

free(NULL);

在个人经验中,大概99% 的C 和Objective-C程序员将会说”no”。一般这样校验:

if(ptr)     free(ptr);

可是若是你检查代码,它是好的。 free()函数释放ptr指向的内存。若是ptr是一个NULL指针,没有操做会被完成。

 

若是人们常常获得一些和这个一样简单的错误,咱们还有什么但愿在更复杂的状况下。

 

Swift 在一个一致性模式下解决这个问题,方法是借助于产生全部的非可变类型而且容许任何类型变成可变的。

 

对于Objective-C里指针类型,这样解决了由nil和NULL致使的不一致。若是一个参数能够接受nil,那么它将会被声明成一个可变类型。反之不能够。编译器能够轻松检查你的代码以确保它作正确的事情。一样的,若是一个返回值能够为nil,很明显它将会是一个可变类型。一般状况下,你的类型将会是不可变的,让它清空它曾经持有的东西。

 

由于任何类型可能成为可变类型,这也将解决一些别的问题。框架中的许多方法返回指定的标记值用来标识一个返回值。例如indexOfObject: 返回NSNotFound,仅仅是NSIntegerMax的别名。这个很容易忘记:

NSUInteger index = [array indexOfObject: obj]; [array removeObjectAtIndex: index]; // oops

 

Swift 中等价的代码将会返回一个可变类型:

func indexOfObject(obj: T) -> Int?

没有找到对象将会返回一个非整数值,而且事实上可变类型是一个不一样的类型,意味这编译器能够帮你检查一切。

 

多个返回值

Swift 容许一个函数返回多个值。或者若是你以一个不一样的方式看到它,Swift 函数老是返回一个值,可是那个值多是一个容易获得的数组。

 

C 和Objective-C用两种方式支持多值,都不太棒。函数/方法能够返回一个包含多个值的结构体或者类,或者他们可使用外部参数。返回一个结构体是如此笨重,因此基本上历来不会被使用,除非结构体理论上是一个单独的单位,像frame返回一个NSRect 或者CGRect。外部的参数在Cocoa中用的不少,虽然,特别为了错误句柄而设计。

 

NSError 类和对应的NSError ** 模式在10.2时代被展示而且快速普及。一些语言抛出异常,Cocoa 把一个NSError * 经过参数传递给调用者来指出错误。像这样的代码是很常见的:

NSError *error; NSData *data = [NSData dataWithContentsOfFile: file options: 0 error: &error]; if(data == nil) {     // handle error }

这个将会变得麻烦,而且错误老是可变的,许多代码将会像这样替代:

NSData *data = [NSData dataWithContentsOfFile: file options: 0 error: NULL];

这有点糟糕,可是有吸引力。

 

多个返回值显示除了异常和外部指针外的另外一个选项。在Python 中,例如,代码将会看起来这样:

data, error = NSData.dataWithContentsOfFile_options_error_(file, 0, None) if not data:     # handle error

由于桥接,这变的有点奇怪,你将会必需要传递NONE做为外部参数,甚至它正被转换成第二个返回值。一个本地的Python调用可能看起来像这样: 

data, error = Data.dataWithContentsOfFile(file, 0)

Swift 版本看起来几乎同样的:

let (data, error) = Data.dataWithContentsOfFile(file, 0)

这是一件小事,可是NSError返回在Cocoa 中是很常见的,使一切变得更友好。问题已经足够困扰我,我已经提交preprocessor crimes against humanity,尝试在 Objective-C中构建多返回值,而且我不须要作其余的任何事情。(我认为一个好的状况可能发生,用一个可选的类型返回错误。在任何状况下,我指望研究哪些选项。)

 

泛型

这是重要的一点。Swift 支持泛型类和函数。

 

这打开了许多可能性。一个是容器类如今可能有一个静态类型,在编译阶段,声明他们包括的类型。在Objective-C中,一旦对象进入一个容器,你便失去了静态类型信息。

NSArray *array = @[ @"a", @"b", @"c" ];

咱们可以处理这种状况,但它不是最好的。因为一件事情,它彻底终止了点语法:

NSLog(@"%@", array[0].uppercaseString);

这句编译失败,由于 array[0] 的类型是id而且没有使用点语法。

 

另外一点,它努力保持对东西内部的追踪。这并非一个大的对于你设置并马上使用本地变量的处理。对于一个小的实例变量它可能有点小痛苦,而且可能真的让你由于参数和返回值而烦恼。当你失败而且获得错误类型,结果是一个运行时错误。

 

对于字典来讲,它变得更糟糕,大概有两种类型错误。我有不少名字像_idStringToHamSandwichMap的实例变量,用来帮助我记住键值是NSString 实例以及HamSandwich实例的值。

 

在Swift中,类型不是数组和字典,而是Array<String> 和Dictionary<String, HamSandwich>。当你从数组中得到一个元素的时候,结果不是id (或者Swift中Any/AnyObject对象)。它也链接的很好,因此若是你为了一个全部它的值的列表请求Dictionary<String, HamSandwich> ,结果是一个集合类型,其包括Dictionary<String, HamSandwich> 实例。

 

函数可能也是泛型的。这让你写操做任何类型的函数,而不须要在你传递他们的时候丢失类型信息。例如,你能够用 Objective-C写一个小的帮助函数用来提供一个默认值,在值为nil的时候用。

id DefaultNil(id first, id fallback) {     if(first) return first;     return fallback; }

让咱们忽略了一会不规范?:操做符提供这个精确的行为。

 

这个工做是不错的,可是丢失了类型信息,以至于不能运行。

NSLog(@"%@", DefaultNil(myName, genericName).uppercaseString);

经过使用macros和非标准的__typeof__ 操做符有可能变得更好,可是它会变的惊人的快速。

 

Swift中等价的代码看起来将是像这样的:

func DefaultNil(first: AnyObject?, fallback: AnyObject) -> AnyObject {     if first { return first! }     return fallback }

Swift 的严格的静态类型会使得使用这个变得痛苦,比起Objective-C更是如此,其丢失了类型信息,可是基本上仍是信任咱们所说的类型。然而,借助于泛型,咱们能够轻易解决这个问题。

func DefaultNil<T>(first: T?, fallback: T) -> T {     if first { return first! }     return fallback }

这样保存类型以便于返回值有一样的参数类型。类型推断意味着你甚至不须要告诉它要使用的类型,它仅仅知道参数类型便可。

 

泛型是另外一个个人preprocessor crimes against humanity 的例子,尝试将他们嵌入到Objective-C中,而且我为可以真正有这些特性而高兴。

 

类型推断

静态输入是不错的,可是它可能在代码中引发多余的困恼:

NSString *string = @"hello"; UIAlertView *alert = [[UIAlertView alloc] initWithTitle...];

编译器知道这些表述内容的类型,因此为何咱们必须每次都重复那些信息?有了Swift,咱们能够不用重复:

let string = "hello" let alert = UIAlertView(...)

尽管对于像JavaScript这样的语言从表面看,有基本的类型变量,仅仅是类型不做为声明的一部分被写出来。若是有一种状况,你真正想要确保那个变量是一个肯定的类型(若是初始化不匹配,会产生一个编译器错误),你仍然能够声明它:

let string: String = ThisMustReturnAString()

编程冗长从某种程度来讲多是好事,有助于提升可读性。可是Objective-C 和Cocoa 可能将其带入一个荒谬的极端,若是有选项用来减小却是不错的。

 

Class-like结构体

像Objective-C同样,Swift有类和结构体。和Objective-C不一样的是,Swift 结构体能够包括方法。代替一个大规模的用来操做结构体的函数集,一个Swift 结构体可能仅仅包括做为方法的代码。CGRectGetMaxX(rect) 能够变成 rect.maxX.

 

该语言也提供一个默认的初始化方法(若是你不提供一个),这能够省去你为了初始化而编写代码的麻烦,而且摆脱像CGRectMake这样难看的函数。

 

此外,结构体被更好地整合进该语言。在 Objective-C中,在结构体和语言之间有一个难看的分隔。声明语法是不一样的,你不能在没有装箱的状况下借助于Cocoa 集合使用他们,而且在用ARC的时候,将Objective-C对象做为结构体的成员会变得极其不方便。

 

在Swift中,声明语法是同样的。由于相应的集合使用泛型,他们没有包含结构体的问题。对象成员执行起来也不困难。他们基本变成按值传递而不是一个彻底不一样的对象。

 

结果是小的模型类在Swift中变得更好。你曾经多少次借助于几个固定的键值来表明一个相关的本应该是一个类的数据集?若是你喜欢我,那么答案就是“太多了”。那是由于生成一个简单的Objective-C类有点小小的痛苦。在ARC以前这是特别真实的,可是甚至使用ARC,它也是很烦人的。你仅仅能够把属性扔给一个类而且调用它一天,固然:

@interface Person : NSObject @property (copy) NSString *name; @property NSUInteger age; @property (copy) NSString *phoneNumber; @end @implementation Person @end

可是,如今它是可变的,因此你必须但愿没人决定开始改变他们。而且没有初始化,因此你必须经过几行代码设置实例变量,但愿你别忘记他们。若是你想要一个好的不可变的模型类,你必须手动写出一个初始化的函数而且保持它和相关属性的同步。

 

有了Swift,这个简单了:

struct Person {     let name: String     let age: Int     let phoneNumber: String }

这个自动生成的initializer和实例全是不可变的。做为奖励,一旦结构体能够潜在地被内联分配内存,而不是须要在堆上分配,它也将会更有效。

 

尾部闭包

这是一个一流的特色,虽然不多被指出可是真的很好。当一个函数或者方法的最后一个参数是闭包的时候(即一个block),Swift 容许你在调用以后写block,在括号外,两种调用是等价的:

dispatch_async(queue, {     println("dispatch!") })  dispatch_async(queue) {     println("dispatch!") }

 

若是闭包仅仅是一个参数,那么你甚至不须要括号:

func MainThread(block: Void -> Void) {    dispatch_async(dispatch_get_main_queue(), block) }  MainThread {     println("hello from the main thread") }

这是有意思的,由于它拆除了一个在语言编写和库调用之间的障碍。当我在2008年讨论的时候,对于Objective-C来讲blocks是一个很棒的补充,由于他们容许你写你本身的控制结构。它借助于GCD有一个大的用途,其可以提供许多有用的像语言构建那样的异步调用,可是做为库调用来实现。然而,使用Objective-C,在如何调用你写的block和如何使用内建的语言结构上仍旧有一个语法上的区别。

 

例如,Swift (至少当前)缺乏像Objective-C的@synchronized语法。尾部闭包意味着你可以实现你本身的,而且使其看起来很像内建的实现方式。

synchronized(object) {     ... }

 

若是你正在用Objective-C来作这个事情,括号不能到达合适的地方,而且你略尴尬地终止一些事情。

synchronized(object, ^{     ... });

 

这是一个小事情,由于它仍然运行良好,而且易于阅读。但同可以以更天然的方式组织代码是不错的事情。

 

运算符重载

我想象这将会是有争议的。不幸的是,有不少程序员已经被C++中严重的操做符重载文化所影响。当某种语言设计者思考为了IO重载<< 和>>位转换操做符,那么你知道你已经在困境中了。我不惧怕去责怪人。

 

然而,我认为若是你超越C++,状况会好得多。操做符重载存在于许多语言中,没有像C++这样疯狂滥用。

 

当人们仅仅使用任意标志来代替那些操做符,操做符重载就是有危害的。当人们使用它以致于传统的操做符可以以新的形式被用做他们传统的目的,就是一件不错的事情。

 

举一个简单的Objective-C 例子:

NSNumber *count = ...; count = @([count intValue] + 1);

 

你能够像这样写代码,即若是数量被保存在一个字典或者一个数组中。处理装箱或者拆箱仅仅增长数目将会是一件痛苦的事情。一旦NSNumber 的值可以超过最大整型值,就会变得有点危险。你能够写一个方法完成那个事情,可是它仍然不太棒。

count = [count add: @(1)];

 

借助于操做符重载,你可以实现 让+, +=, 和++来 作和操做一个标准整型值一样的事情。代码将会变成:

NSNumber *count = ...; count++;

 

固然,Swift中的泛型本意是NSNumber自身一般更少有这种须要,可是有许多其余的潜在的运算符重载的需求。Swift没有内建的任意大小的整型,可是你可以本身实现一个用来完成全部的标准运算的操做符。

 

一个真正有意思的例子是创建一个自动布局约束,做为SwiftAutoLayout的示例。这个使用操做符重载将表达式view1.al_left() == view2.al_right() * 2.0 + 10.0 转换成NSLayoutConstraint 的实例。这是一个好的方式,比苹果官方的更酷,可是成了疯狂的视觉格式的语言。

 

除了重载已有的操做符以外,Swift 容许你利用限定字符集声明全新的操做符。若是你真的想作,你能够调用<~^~>创建一个操做符而且使其作你想作的事情。看到人们这样作将会是有意思的,咱们将会必须很是当心以便于让这种能力不会上瘾。

 

结论

Swift 是一种咱们期待的那种用来实际作事的很是有意思的新的语言。它有许多不一样于Objective-C的特色,可是我认为这些最终将会很好用。

 

这就是今天要讲的。下次带来更多的有意思的东西。我计划继续探讨Swift 一段时间。若是你有一些你想要明白的关于Swift 的话题,那固然很棒了!若是关于一些事情,我能够将它放在文件中,方便阅读。另外一种方式,若是你有一个想了解的话题,请发给我

相关文章
相关标签/搜索