Swift要点:从Objective-C开发者的角度看Swift

代码环境是Xcode6.3-Beta3.

Swift已经极大的改变了开发iOS应用的方式。本文中,我会列出Swift的几个重点,并且和Objective-C一一做出对比。

注意,本文不是Swift的入门指导。苹果发布了Swift Programming Language,强烈建议您读一读这本书。文本主要介绍Swift中比较酷炫的特性。

类型

Swift提供的第一个重大的改进是类型推断。使用了类型推断的编程语言,开发者不需要在声明中明确指定变量的类型。编译器会自动探知要赋给变量的值的类型。比如下面的例子,编译器会自动设定变量的类型为String:

// 类型推断
var str = "Hello World!"

// 明确定义类型,这里可以不这样
var swift: String = "Hello Swift"

和类型推断一起带来的是类型安全。在Swift中,编译器(一般来说全部,但是在很少的情况下)知道一个类型的全部类型。这样给编译器一个选择如何编译代码的机会,因为编译器有足够的信息。

这承托出了Objective-C的一个非常动态的本质。在Objective-C中,任何类型在编译期间都是未知的。这也是为什么你可以在运行时给已经存在的类添加方法,添加一个全新的类型,甚至于改变一个对象的类型。

看看Objective-C的代码:

Person *man = [[Person alloc] init];
[man sayHello];

当编译器看到对方法sayHello的调用的时候,它就会检查类型Person类的头文件中是否有一个叫做sayHello的方法。如果没有就报错。编译器就只可以做这些。这确实可以你可能引入的最初简单的bug:如拼写错误。但是,因为动态性,编译器不可能知道sayHello方法是否会在运行改变、甚至是否存在。这可能是一个协议中得optional方法。比如(还记得那些使用respondsToSelector的检查吗)。

由于缺乏强类型, 在Objective-C中调用方法的时候编译器几乎不能做什么优化。处理代码动态分发的方法是:objc_msgSend。我确定你在很多的时候见过这个方法。在这个方法中,寻找selector并调用。你不能说这没有增加复杂度。

再回头看看Swift的实现:

var p = Person()
p.sayHello()

在Swift中,编译器在调用方法的时候知道类型的更多信息。它准确的知道sayHell方法在哪里定义的。因此,Swift可以在方法调用的时候直接调转到方法定义的地方,而无需经过任何的动态分发过程。在其他的情况下,Swift也可以使用vtable风格的分发方式,比Objective-C的动态分发花费小得多了。这也是C++的虚方法使用的分发机制。

Swift的编译器也非常的有用。可以排除细微的类型相关的bug。它也能使你的代码运行的更快。

泛型

Swift带来的另一个很大的改变是泛型。如果你对C++很熟悉的花,那么你可以把这些和C++的template做类比。因为Swift是类型严格的,你必须方法可以接受的参数的类型。但是有的时候你的方法对于不同的类型来说作用是相同的。

比如,你希望把一对值存放在一起。你可以用Swift实现存放整型数值的结构:

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: 1, b: 1)
println("\(intPair.a)" + "\(intPair.b)" + " equal: \(intPair,equal())")

Sort of useful. But now you want this to work for floating point numbers as well. You could define aFloatPair class, but that would look awfully similar. This is where generics come in. Instead of declaring a whole new class, you could simply do this:

有点用处,但是你也想让这个可以用于浮点数。你可以定义一个FloatPair类, 但是实在是太相似了。这个时候泛型就会发挥用处了。相对于定义一个全新的类型,你可以这样:

struct Pair<T: Equatable> {
    let a: T!
    let b: T!
    
    init(a: T, b: T){
        self.a = a
        self.b = b
    }
    
    func equal() -> Bool {
        return a == b
    }
}

let tempPair = Pair<Int>(a: 1, b: 1)
var tempA = tempPair.a
var tempB = tempPair.b
var tempEqual: Bool = tempPair.equal()

println("\(tempA) " + "\(tempB) " + "equal: \(tempEqual)")

let tempPair1 = Pair<Float>(a: 1.0, b: 1.0)
var tempA1 = tempPair1.a
var tempB1 = tempPair1.b
var tempEqual1: Bool = tempPair.equal()

println("\(tempA1) " + "\(tempB1) " + "equal: \(tempEqual1)")

请各位读者原谅我用了这么烂得命名方式啊。。。

非常有用!你可能不是很清楚你为什么需要泛型,但是相信我会有很多时候你会用到。你很快会发现在哪里可以使用这些代码。

容器

也许你觉得NSArray和NSDictionary和对应的可变类型都已经很好用了。但是你需要学习对应的Swift容器。幸运的是,他们非常相似。如:

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

这些对你来说已经非常熟悉了。瞅一眼就可以搞定的。在Objective-C中,数组和字典可hi包含你想要的任何类型。但是在Swift中数组和字典是强类型的。而且这些容器都是泛型类型的。

上面的两个变量可以使用类型表达式来重写(你不是必须要这么做),比如:

let array: Array<Int> = [1, 2, 3, 4, 5]
let dictionary: Dictionary<String, Int> = ["dog": 1, "elephant": 2]

Notice how generics are used to define what can be stored in the container. There is also a short form for these, which is slightly more readable, but essentially boils down to the same thing:

注意泛型是怎么定义什么可以放进容器中的。定义的方式也有简短的方式的,可读性更好。但是本质上来说是一样的:

let array: [Int] = [1, 2, 3, 4, 5]
let dictionary: [String, Int] = ["dog": 1, "elephant": 2]

注意,这个时候你无法给数组添加任何的不是Int类型的值。这看起来不好,但是非常有用。你不再需要在文档中专门指明属性返回的数组中存放的是什么类型的值。编译器会自动检测错误并优化代码的编译。

可变性

Swift容器的一个有意思的地方是他们都是可变的(mutable)。array和dictionary没有“mutable”的对应类型了。可变和不可变都由letvar关键字控制。let就是用来声明一个常量的关键字,var是用来声明一个变量的。let就像是C、C++或者Objective-C中得const关键字一样。

The way this relates to collections is that collections declared using let cannot change size. That is, they cannot be appended to, or removed from. If you try to, then you get an error like so:

使用let声明的集合不能再修改size,也就是不能再append或者remove。如果你这么做了,就会报错:

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

字典也同样使用这个规则。这允许编译器推测出这一类型的集合并作出相应的优化。如果size不能修改,集合的存储实现不再需要重新分配新的空间容纳新的值。因此,经常使用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."];
}

This is quite tedious and contains a lot of characters that are nothing to do with the data being manipulated. The same in Swift would look like this:

这样的写法非常的冗长,而且包含了与本次操作无关的其他字符。同样的功能在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-case表达式中。更多相关请继续收看下一节内容。

Swift的字符串的另一个好东西是本地支持Unicode。你可以使用任意的Unicode字符,即使是在变量名和函数名中。如果你愿意你可以声明一个方法???? (pile of poo!)

另一个字符串的好东西是一种内建的方法计算一个字符串的长度。当字符串全部都是Unicode字符的时候,计算字符串的长度就变得重要了。你不能只是说有多少个byte用于存放UTF8字符串了。因为有的字符一个byte是不够的。在Objective-C中,NSString通过UTF16的个数来计算字符串的长度,2个byte存放一个字符串。但是,有的Unicode字符一次需要用两个2个byte的组合,所以之前NSString的计算方式不再能正确的表明字符串中字符的个数。

Fortunately, Swift has a handy function to calculate the true number of code-points in a string. It uses the top level function called countElements(). You would use it like so:

幸运的是,Swift有一个很好用的方法可以计算字符串的字符个数。这需要用到一个全局的countElements()方法。使用方法:

var poos = "\u{1f4a9}\u{1f4a9}"
//countElements(poos) // 2
count(poos) // 2

在Swift1.2中使用count方法

如你所见,Swift的字符串真的是非常好用!

Switch表达式

本文的最后需要简单的介绍一下Swift的switch表达式。Swift的switch表达式对Objective-C的switch表达式进行了极大的升级。因为,不打破Objective-C这个C语言的超集的情况是不能有大的升级的。

The first exciting feature is switching on strings. This is something that you may have wanted to do before, but couldn’t. To “switch” on strings in Objective-C you had to use lots of if-statements withisEqualToString: like so:

第一个就是switch表达式可以使用字符串。这也许是你早就想要的一个功能。在Objective-C中你不得不使用很多的if-else表达式和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上使用string以外,还有一些有意思的地方。swtich表达式中是没有break的。这是因为Swift的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的问题。

下一个有意思的地方是...和..<。这些新的操作符是用来定义range的。前面的而一个是包含右边的数字。后面的一个是不包含右边的数字。这非常有用。

接下来学习什么?

希望本文能让你对Swift语言让人兴奋的特性有一个认识。但是还有更多的东西需要学习。你可以看苹果的文档,这回帮助你更好的学习这个新的语言。如果你觉得翻译的又什么问题请点击这里查看原文。