[关闭]
@wrlqwe 2016-05-23T03:07:33.000000Z 字数 12381 阅读 933

Why Swift

Swift iOS


前言

两年前的6月3日,WWDC上,Apple发布了全新的Swift语言用于未来的苹果软件开发,刚刚推出时,虽然有着丰富而强大的语法特性,但是并不成熟。现在Swift3.0即将发布,从1.0到2.0到2.2,Swift已经渐渐稳定,API和语法上的更改逐渐变少。

不用Obj-C的理由越来越多,不用Swift的理由越来越少,现在,我将尝试在下面几个方面,

类型体系
扩展能力
语法
函数式

向你介绍Swift的强大之处~

强大的类型系统

Swift兼容绝大部分的Obj-C的类型和C的类型,与此同时,它引入了全新的类型体系。

Swift的类型分为值类型和引用类型,与Obj-C不同的是:Obj-C的对象全部都是引用类型,struct和基本类型都是C的特性,而在Swift当中,除了struct,还有enum和元组,所有的基础数据类型也都归为了struct,平时常用的数据类型:String、Array、Dictionary,也都是struct类型。

非一般的值类型

不同于引用类型,对于值类型,声明一个新的值类型就是初始化一个新的实例:

  1. struct S { var data: Int = -1 }
  2. var a = S()
  3. var b = a // b是a的拷贝
  4. a.data = 42 //更改a的数据,b的不受影响
  5. print("\(a.data), \(b.data)") //42, -1

这往往能帮助理清代码,因为不用担心实例会被逻辑外的其他代码改动。同时,在多线程环境下,使用值类型可以安全的传递变量,不需要特地同步。

由于值类型的安全性,struct被广泛用在Swift语言中,包括基础的String和容器的Array和Dictionary,都被设计为struct。这些类型在保持了对传统OC对象API的兼容性之外,还做出了丰富的扩展,在OC和Swift中间,它们可以自由转换,作为容器的Array和Dictionary甚至无需包装可以直接存取值类型。

enum也是程序设计中不可或缺的元素,在Swift中,enum的值不但可以是Int类型,同时还能支持String、Float、Bool类型:

  1. enum Months: String {
  2. case Jan, Feb
  3. }

enum还可以包裹一个值,初学者很头疼的Optional类型就是一个enum:

  1. public enum Optional<Wrapped> : _Reflectable, NilLiteralConvertible {
  2. case None
  3. case Some(Wrapped)
  4. }

另外,enum在模式匹配中也扮演着重要的角色。

类型推断

Swift是一门类型安全的语言,类型安全语言需要代码里值的类型非常明确。如果你的代码中有部分值需要String类型,你就不能错误地传递Int。

鉴于Swift的类型安全,编译代码时,Swift会执行类型检查并将任何类型不匹配的地方标记为错误,使你在开发当中尽可能早的捕获并修正错误。
类型检查有助于你在操作不同值的类型时避免犯错。但这并不意味着你必须在声明每一个常量或变量时去检查类型,如果你不检查所需值的类型,Swift会执行类型推断来计算出相应地类型。

类型推断让编译器在编译代码时,根据你提供的值,自动推测出特定表达式的类型。

基于类型推断,Swift对类型声明的需要远比C或Objective-C语言要少得多。常量与变量仍然有明确的类型,但明确指定类型的工作已经由编译器代你完成。

比如,初始化一个变量时,如果不指定具体的类型,数字12会被推断为Int:

  1. let monthsOfYear = 12 //Int类型

对于集合类型,编译器可以根据元素自动推断出合适的类型:

  1. let arr0 = [1, 2] //[Int]
  2. let arr1 = [1, 2, 3.0] //[Double]
  3. let arr2 = [1, 2, true] //[Number]
  4. let arr3 = [1, 2, "3"] //[NSObject]

编译器的类型推断在保证了类型安全的同时,减少了繁琐的声明,在实际使用中,完全可以不假思索的写出 let variable = value 的代码,非常省心省力。

泛型和类型约束

泛型是现代编程语言的重要特征,相对于Obj-C,基于Swift的泛型特性,你能够写出扩展性更强、复用性更强的方法、类型,它可以让你尽可能避免重复代码,用一种清晰和抽象的方式来表达代码的意图。

泛型函数

下面是一个典型的泛型:

  1. func exchangeValue<T>(inout left: T, inout right: T) {
  2. (left, right) = (right, left)
  3. }
  4. var leftVal = 100
  5. var rightVal = 200
  6. exchangeValue(&leftVal, right: &rightVal)
  7. print("\(leftVal), \(rightVal)") // 200, 100

这个例子里,我们通过inout定义了可以改变参数值的函数, 然后利用元组,实现了值交换的逻辑。当然,他们要针对同一个泛型T的实例。

在Swift中,泛型所能做的事远远不止这些。

泛型类型

接下来,我们通过泛型特性,实现一个典型的Stack构造。

Stack是程序设计里一个典型的数据结构,它跟Array类似,但是功能上与Array并不想吐,它存储一个队列,遵循先进后出的原则存储数据。

Stack

除了泛型函数,Swift还允许你定义自己的泛型类型。通过自定泛型类型,我们可以很容易扩展类型的适用范围,更好地复用代码。

  1. struct Stack<T> {
  2. var items = [T]()
  3. mutating func push(item: T) {
  4. items.append(item)
  5. }
  6. mutating func pop() -> T {
  7. return items.removeLast()
  8. }
  9. }

关联类型

之前阐述的protocol里的所有类型都是确切的,Swift允许你在protocol中使用类似于泛型函数泛型类型的泛类型,这就是关联类型(Associated Types)。

  1. protocol Container {
  2. associatedtype ItemType
  3. mutating func append(item: ItemType)
  4. var count: Int { get }
  5. subscript(i: Int) -> ItemType { get }
  6. }

Container协议定义了三个任何容器必须支持的兼容要求:

这个Container协议没有指定容器中的元素是如何存储的,也没有指定容器可以存储的元素类型,但是限制了append方法的形参类型必须和subscript返回值类型一致。这种限制构成所谓的关联类型

接下来,我们为Stack实现Container协议:

  1. struct Stack<T>: Container {
  2. var items = [T]()
  3. mutating func push(item: T) {
  4. items.append(item)
  5. }
  6. mutating func pop() -> T {
  7. return items.removeLast()
  8. }
  9. mutating func append(item: T) {
  10. items.append(item)
  11. }
  12. var count: Int {
  13. return items.count
  14. }
  15. subscript(i: Int) -> T {
  16. return items[i]
  17. }
  18. }

泛型Where约束

类型约束使得Swift的泛型更加强大,但是还不够灵活。想象一个应用场景。某个函数接受两个参数,这两个参数都要求遵循Container协议,除此之外,还都要求这两个参数(集合类型)的元素类型相同,如何实现?单纯的类型约束是办不到的,好在Swift为我们带来了where语句。

where语句的目的很直接,增强了「泛型」的威力。根据我的理解,where应该属于那种约束少、灵活大的语言特性,关于它的使用想必非常繁杂。

下面举个例子引出where的应用场景。定义一个名为allItemsMatch的泛型函数,顾名思义,该函数用来检查两个Container是否包含相同顺序的相同元素,如果所有元素顺序相同且值相同,则返回true,否则返回false,如下:

  1. func allItemsMatch<
  2. C1: Container, C2: Container
  3. where C1.ItemType == C2.ItemType, C1.ItemType: Equatable>
  4. (someContainer: C1, anotherContainer: C2) -> Bool {
  5. // check that both containers contain the same number of items
  6. if someContainer.count != anotherContainer.count {
  7. return false
  8. }
  9. // check each pair of items to see if they are equivalent
  10. for i in 0..<someContainer.count {
  11. if someContainer[i] != anotherContainer[i] {
  12. return false
  13. }
  14. }
  15. // all items match, so return true
  16. return true
  17. }

泛型函数allItemsMatch头部信息告诉我们:

其中,后两部分内容定义在Where语句中,作为C1和C2的约束。

allItemsMatch(_:_:)方法用起来是这样的:

  1. var stackOfStrings = Stack<String>()
  2. stackOfStrings.push("uno")
  3. stackOfStrings.push("dos")
  4. stackOfStrings.push("tres")
  5. var arrayOfStrings = ["uno", "dos", "tres"]
  6. if allItemsMatch(stackOfStrings, arrayOfStrings) {
  7. print("All items match.")
  8. } else {
  9. print("Not all items match.")
  10. }

详情点我

强大的扩展 & 完全的面向协议

在Obj-C中,protocol支持约束NSObject的子类,Swift在全部支持Obj-C的基础上,扩展了自己的类型。

在Swift中,protocol不但可以使用在class里,还能为struct和enum等几乎所有类型扩展方法,这使protocol的作用被放大了数倍,不再仅仅担任接口定义的角色。

更激动人心的是: Swift中的基本类型例如Int、Float都是以struct实现,所以你甚至可以为基本数据类型添加方法和属性(当然是计算属性),说Swift是一个完全面向协议的语言毫不为过。

基本数据类型的扩展

我们接下来为Int添加扩展,使它可以根据自己的值执行N次操作:

  1. extension Int {
  2. func times(@noescape action: () -> ()) {
  3. for _ in 0..<self {
  4. action()
  5. }
  6. }
  7. }
  8. 3.times {
  9. print("test")
  10. }

在这里,我们定义了times方法,使其可以执行N次action。

其中@noescape修饰闭包告诉编译器,这次闭包的调用在函数返回前就已经结束了,在闭包里引用self是安全的,不必声明为unowned或者weak。

同样的,我们也可以为dispatch_queue_t实现扩展:

  1. extension dispatch_queue_t
  2. {
  3. final func async(block: dispatch_block_t) {
  4. dispatch_async(self, block)
  5. }
  6. final func sync(block: dispatch_block_t) {
  7. dispatch_sync(self, block)
  8. }
  9. }
  10. dispatch_get_main_queue().async {
  11. }

协议的默认实现

继承和组合的缺点

在开发工作中,继承总是用来在多个类之间共享代码。
设想一个场景,一个人类,可以说话和睡觉,而一个Worker,除了上述功能,还可以工作。

解决方案很简单,我们可以 Person 和 Worker 之间建立继承关系:

  1. class Person {
  2. func say() {
  3. print("hello")
  4. }
  5. func sleep() {
  6. print("I'm sleeping")
  7. }
  8. }
  9. class Worker: Person {
  10. func work() {
  11. print("I'm carrying bricks")
  12. }
  13. }

在随后的开发中,可能一个新的类型

Robot,也可以工作了,这个时候我们不能使用 Worker 了,因为很明显, Robot不可能同时是一个Person。

解决这个问题,我们可能尝试组合的方式,通过公共类管理 work 行为:

  1. class WorkManager {
  2. func work() {
  3. print("I'm carrying bricks")
  4. }
  5. }
  6. class Worker: Person {
  7. let workManager = WorkManager()
  8. func work() {
  9. workManager.work()
  10. }
  11. }
  12. class Robot: Machine {
  13. let workManager = WorkManager()
  14. func work() {
  15. workManager.work()
  16. }
  17. }

这样做的缺点也显而易见,代码虽然复用了,可是类结构也变的臃肿,每次都要引用 WorkManager 。

默认实现解决问题

在Swift2.0里在定义一个协议protocol时,还能使用extension给它的某些方法做默认实现:

  1. protocol Workable {
  2. func work()
  3. }
  4. extension Workable {
  5. func work() {
  6. print("I'm carrying bricks")
  7. }
  8. }

有了上面的代码,当你创建一个遵从 Workable 协议的类或者是结构体时,就能获得 work() 方法。

这只是一个默认的实现方式。因此你可以在需要的时候重新定义这个方法;如果不重新定义的话,会使用这个默认方法。

使用这种方式,可以大大简化我们的代码,我们甚至什么都不需做,指定继承关系就完成了工作:

  1. class Worker: Person, Workable {
  2. }
  3. class Robot: Machine, Workable {
  4. }
  5. ...
  6. let worker = Worker()
  7. let robot = Robot()
  8. worker.work() // I'm carrying bricks
  9. robot.work() // I'm carrying bricks

当然我们也可以设定协议依赖的数据,比如work依赖对象的名字:

  1. protocol Workable {
  2. var name: String { get }
  3. func work()
  4. }
  5. extension Workable {
  6. func work() {
  7. print("\(name) is carrying bricks")
  8. }
  9. }
  10. class Worker: Person, Workable {
  11. var name: String
  12. init(name: String) {
  13. self.name = name
  14. }
  15. }
  16. ...
  17. let worker = Worker("Tiezhu")
  18. worker.work() // Tiezhu is carrying bricks

现在可以按照功能重新划分protocol,并开启积木模式了, 首先拆分 Person :

  1. protocol Sayable {
  2. var words: String { get }
  3. func say()
  4. }
  5. extension Sayable {
  6. func say() {
  7. print("\(words)")
  8. }
  9. }
  10. protocol Sleepable {
  11. var name: String { get }
  12. func sleep()
  13. }
  14. extension Sleepable {
  15. func sleep() {
  16. print("\(name) is sleeping")
  17. }
  18. }

enjoy it :

  1. class Person: Sayable, Sleepable {}
  2. class Worker: Person, Workable {
  3. var name: String
  4. var words = "hello"
  5. init(name: String) {
  6. self.name = name
  7. }
  8. }
  9. class Robot: Sayable, Workable {
  10. var name: String
  11. var words = "..."
  12. init(name: String) {
  13. self.name = name
  14. }
  15. }
  16. class Cat: Sayable, Sleepable {
  17. var name: String
  18. var words = "meow~"
  19. init(name: String) {
  20. self.name = name
  21. }
  22. }
  23. ...
  24. let tiezhu = Worker(name: Tiezhu)
  25. tiezhu.work() // Tiezhu is carrying bricks
  26. let robot = Robot(name: T1000)
  27. robot.work() // T1000 is working
  28. let feifei = Cat(name: "feifei")
  29. feifei.say() // meow~

翻开Swift类型的AppleDoc,我们可以看到对各个类型数不胜数的扩展和协议实现, 包括基础类型和集合、String等类型,每一个都有各式各样的扩展:

  1. public struct Int : SignedIntegerType, Comparable, Equatable {
  2. ...
  3. }
  4. extension Int : RandomAccessIndexType {
  5. ...
  6. }

甚至连==这种操作符都由protocol定义,配合上泛型和类型推断,这些共同打造出了类型安全、功能强大的Swift。

神奇有效的语法

模式匹配

模式匹配是指定一种模式,然后尝试识别符合模式的变量,常用来过滤数据,能够避免深层次的if else或者swich判断,提高代码的可维护性。

在Swift中,使用 ~= 来表示模式匹配的操作符。如果我们看看 API 的话,可以看到这个操作符有下面几种版本:

  1. public func ~=<T : Equatable>(a: T, b: T) -> Bool
  2. public func ~=<I : ForwardIndexType where I : Comparable>(pattern: Range<I>, value: I) -> Bool
  3. /// Returns `true` iff `pattern` contains `value`.
  4. public func ~=<I : IntervalType>(pattern: I, value: I.Bound) -> Bool
  5. public func ~=<T>(lhs: _OptionalNilComparisonType, rhs: T?) -> Bool

在实际操作中,我们通常使用switch case来应用模式匹配:

解构

最基本的用法就是解构,使用switch case 帮助将enum或者元组里包裹的内容解析出来:

  1. let aTrade = Trades.Sell(stock: "GOOG", amount: 100, stockPrice: 666.0, type: TraderType.Company)
  2. switch aTrade {
  3. case let .Buy(stock, amount, _, TraderType.SingleGuy):
  4. processSlow(stock, amount, 5.0)
  5. case let .Sell(stock, amount, _, TraderType.SingleGuy):
  6. processSlow(stock, -1 * amount, 5.0)
  7. case let .Buy(stock, amount, _, TraderType.Company):
  8. processFast(stock, amount, 2.0)
  9. case let .Sell(stock, amount, _, TraderType.Company):
  10. processFast(stock, -1 * amount, 2.0)
  11. }
  1. switch (4, 5) {
  2. case let (x, y): print("\(x) \(y)")
  3. }

Where条件匹配

我们也可以为匹配添加条件,以便更精确的匹配:

  1. let aTrade = Trades.Buy(stock: "GOOG", amount: 1000, stockPrice: 666.0, type: TraderType.SingleGuy)
  2. switch aTrade {
  3. case let .Sell(stock, amount, price, TraderType.SingleGuy)
  4. where price*Float(amount) > 1000000:
  5. processFast(stock, -1 * amount, 5.0)
  6. }

值匹配

  1. let age = 23
  2. let job: String? = "Operator"
  3. let payload: AnyObject = NSDictionary()
  4. switch (age, job, payload) {
  5. case (let age, _?, _ as NSDictionary):
  6. print(age)
  7. default: ()
  8. }

类型转换

值得注意的是: 模式匹配在与switch共用时,只能匹配第一个模式,并不能像一般的switch case语句,同时case多条。

  1. let a: Any = 5
  2. switch a {
  3. case is Int:
  4. print (a as! Int + 1)
  5. case let n as Int:
  6. print (n + 1)
  7. default: ()
  8. }

重写~=运算符

  1. switch 5 {
  2. case 0..10: print("In range 0-10")
  3. }

对于一个类型

  1. struct Soldier {
  2. let hp: Int
  3. let x: Int
  4. let y: Int
  5. }

重写~=运算符

  1. func ~= (pattern: Int, value: Soldier) -> Bool {
  2. return pattern == value.hp
  3. }

可以这样匹配

  1. let soldier = Soldier(hp: 99, x: 10, y: 10)
  2. switch soldier {
  3. case 0: print("dead soldier")
  4. default: ()
  5. }

for case和guard case

如果仅仅做一次匹配,则可以不使用switch case语法,直接用for case或者guard case。

  1. for case let Entity.Entry(t, x, y, _) in gameEntities()
  2. where x > 0 && y > 0 {
  3. drawEntity(t, x, y)
  4. }

Sample 匹配url

对于多个条件的判断,模式匹配可以发挥巨大的作用。

假如我们有一个url,想要校验匹配它们的全部path部分,除了繁琐的判断,使用模式匹配,它的代码可以很简单:

  1. guard let url = NSURL(string: "https://yeasy.gitbooks.io/docker_practice/content/container/daemon.html") else {
  2. print("wrong url")
  3. return
  4. }
  5. guard let relativePath = url.relativePath else {
  6. print("no relativePath")
  7. return
  8. }
  9. let paths = relativePath.componentsSeparatedByString("/")
  10. var generator = paths.generate()
  11. if case (""?, let second, "content"?, let fourth as String, "daemon.html"?) = (generator.next(), generator.next(), generator.next(), generator.next(), generator.next()) {
  12. print("matched second: \(second!) fourth: \(fourth)")
  13. }

在这里, 我们使用generator来帮助我们枚举paths的各个部分, 仅仅用了一个 if case, 就解决了需要多次判断的问题。

sample 正则匹配

通过前边Soldier的例子,我们已经熟悉了~=运算符,现在我们可以对它进行重写,帮助我们实现正则表达式的匹配:

  1. func ~=(pattern: NSRegularExpression, input: String) -> Bool {
  2. return pattern.numberOfMatchesInString(input,
  3. options: [],
  4. range: NSRange(location: 0, length: input.characters.count)) > 0
  5. }
  6. if let pattern = try? NSRegularExpression(pattern: "^([a-z0-9_\\.-]+)@([\\da-z\\.-]+)\\.([a-z\\.]{2,6})$", options: []) {
  7. let email = "email@rulin.me"
  8. if case pattern = email {
  9. print("binggo")
  10. }
  11. }

函数式编程

Swift对函数式编程也提供了良好的支持,函数式编程强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。

First-Class Function

在Swift中,函数可以是first-calss function, 它可以被赋给一个变量,在开发中,函数和闭包还可以自由的互相转换。

map & filter & reduce

  1. let numbers = [7: 10
  2. , 9: 2, 8: 3, 4: 1, 5: 4]
  3. let newNumbers = numbers.map(+)
  4. print(newNumbers) //[9, 17, 11, 11, 5]

在这个例子中,我们把+函数传递给map方法,此方法把Dictionary的每一对key-value相加,得出新的值,以此组成了数组,因为Dictionary没有顺序,所以最终的array并没有与一开始声明Dictionary的顺序一致。

这里+方法是第一等公民,所以它可以被当做参数传递,而map则是函数式编程中的重要方法,它把老的item映射为新的item,返回一个数组,map方法广泛存在于swift的哥哥类型和协议中。

下面一个例子:

  1. let sum = (0...100)
  2. .map({ num in
  3. return num + 1
  4. }).filter({ num in
  5. return num % 2 == 1
  6. }).reduce(0, combine: { lastResult, newValue in
  7. return lastResult + newValue
  8. })

无副作用是函数式编程重要的特点之一,它提倡每个函数都有返回值,并且函数不要对全局变量进行修改,保证它的行为单一可控,更易于重用。

在这个例子里,map、filter和reduce都是无副作用的,他们每一步都计算出了一个结果,然后依赖这个结果进行下一步的计算。

Currying柯里化

柯里化也是函数式编程中的一个重要概念,它可以轻易把多参数函数map到单参数函数上,组成所有参数后最终惰性求值。

柯里化的方法可以把参数的传递提供给外界,而不用关心具体赋参数的过程,有助于减少代码间的耦合性。

  1. func plus(first first: Int) -> (second: Int) -> (third: Int) -> Int {
  2. return { second -> Int -> Int in
  3. { third in
  4. return first + second + third
  5. }
  6. }
  7. }
  8. let sum0 = plus(first: 10)
  9. //somewhere
  10. let sum1 = sum0(second: 11)
  11. //some another where
  12. let sum2 = sum1(third: 100)
  13. print(sum2) // print: 121

更快的速度

相比于Obj-C,Swift 在于方法调用上的优化使它有了更快的执行速度。在 Obj-C 中,所有的对于 NSObject 的方法调用在编译时会被转为 objc_msgSend 方法进行动态派发,这种方式虽然提供了强大的灵活性,但是确实会对程序运行速度有影响。

Swift 因为使用了更安全和严格的类型,如果我们在编写代码中指明了某个实际的类型的话,我们就可以向编译器保证在运行时该对象一定属于被声明的类型。这对编译器进行代码优化来说是非常有帮助的,因为有了更多更明确的类型信息,编译器就可以在类型中处理多态时建立虚函数表 (vtable),这是一个带有索引的保存了方法所在位置的数组。在方法调用时,与原来动态派发和查找方法不同,现在只需要通过索引就可以直接拿到方法并进行调用了,这是实实在在的性能提升。

当然如果使用NSObject的子类,对性能提升的帮助就不大了。

其他

集成

对于当前已有的项目,Swift的集成非常容易,添加Swift文件时XCode自动引导集成。

Obj-C和Swift之间的互相调用和API转换也已经相当成熟,可以完全可以无缝集成,你可以在新的ViewController里自由的使用Swift,不担心对原有代码的破坏。

需要注意的是,C里的宏定义并不支持转换。

脚本

Swift可以轻易地使用在脚本里,如果你对Shell脚本并不熟悉,不妨使用熟悉的方式进行编写~

更多平台支持

目前Swift已经开源,并且登录了Linux平台,甚至连Android平台也有移植成功的案例。
作为Obj-C的继任,Swift值得更多关注。

衰老的Objective-C

Objective-C是一门古老的语言,推出至今已经超过30年了,虽然几经修缮,但是该落伍的迟早要落幕,而初生的Swift现在也慢慢成熟,是时候完全抛弃Obj-C了。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注