Swift第四天

第四天主要关注控制流,函数及闭包。

目前见过最灵活的Switch

Swift的 switch 语句设计是我见过功能最强大,最灵活的 switch 语句。甚至我觉得语言设计者的意图是让我们少使用 if...else... (也可能是我的错觉😀)

Swift switch 语句的强大主要体现在 case 匹配十分强大:

case 语句匹配的模式除了支持数值,字符,字符串,还支持区间匹配,元组匹配。

区间匹配

let a = Int.random(in: 1...20)

switch a {
case 1...10:
   print("1到10之间")
default:
   print("11-20")
} 

元组匹配

let b = (0, 1)
switch b {
case (1, 0):
   print("First item is 0.")
case (0, 1):
   print("Second Item is 1")
default:
   print("We don't known")
} 

当然我们也可以在元组中放区间匹配

let b = (0, 1)
switch b {
case (1, 0):
   print("First item is 0.")
case (-1...1, 0...10):
   print("Second Item is 1")
default:
   print("We don't known")
} 

甚至我们可以使用省略符 _ 匹配所有可能的值

let b = (0, 1)
switch b {
case (0, _):
   print("First item is 0.")
case (_, 1):
   print("Second Item is 1")
default:
   print("We don't known")
} 

swift还可以在 case 分支中做值绑定,这允许我们获取到我们匹配的值

let b = (0, 1)
switch b {
case (let x, 1):
   print("First item is \(x).")
case (_, 0):
   print("Second Item is 0.")
default:
   print("We don't known")
} 

你甚至还可以在分支语句中使用 where 做表达式判断

let b = (0, 1)
switch b {
case (1, _):
   print("First item is 1.")
case (_, 0):
   print("Second Item is 0.")
case let (x, y) where x != y:
   print("\(x)!=\(y)")
default:
   print("We don't known")
}

我想我以后要在swift中多用 switch ,感觉超灵活的!!!

它的强大还远不止此, case 还支持复合匹配:

let c = "a"

switch c {
case "a", "e", "i", "o", "u":
   print("元音字母")
default:
   print("其他字母")
}

你还可以元组+值绑定+复合匹配:

let c = (0, 9)
switch c {
case (let x, 0), (0, let x):
   print(x)
default:
   print("others")
}

swift的switch没有隐式贯穿

case语句执行完之后默认是直接break的,不需要和C,JavaScript中一样使用显式地使用break。当然如果你需要贯穿,也可以通过控制转移语句 fallthrough 来显式地进行贯穿

var d = 1

switch d {
case 1:
   d += 1
   fallthrough
case 2:
   d += 1
   fallthrough
case 4:
   d += 1
   fallthrough
default:
   print(d)
}

我们可以比较一下JavaScript的

var b = 1;
switch (b) {
   case 1:
   	b = b + 1;
   case 2:
   	b = b + 1;
   case 4:
   	b = b + 1;
   default:
       console.log(b);
} 

标签语句

其他的控制转移语句 break , continue 其实和其他语言中用法别无二致。

但是有一个新奇的标签语句,这可以让控制转移语句转移到他们想转移的点,个人认为也是个很不错的特性:

gameLoop: while square != finalSquare {
   diceRoll += 1
   if diceRoll == 7 { diceRoll = 1 }
   switch square + diceRoll {
   case finalSquare:
       // 骰子数刚好使玩家移动到最终的方格里,游戏结束。
       break gameLoop
   case let newSquare where newSquare > finalSquare:
       // 骰子数将会使玩家的移动超出最后的方格,那么这种移动是不合法的,玩家需要重新掷骰子
       continue gameLoop
   default:
       // 合法移动,做正常的处理
       square += diceRoll
       square += board[square]
   }
}
print("Game over!") 

上面有个官方的例子,switch当中的break和continue都是在控制gameLoop这个循环,如果break和continue没有指定gameLoop的话,他们控制的只是switch语句。

循环

说到循环,主要有 for...in 和 while , repeat...while

for...in 和 while 的功能和写法都和其他语言很相近,而 repeat...while 只是 do...while 的别称。我们不加赘述了。

防御性编程

我经常使用防御性编程,他可以大大提高代码可读性,让代码不会由一层又一层的 if...else... 组成,swift将这个概念以语法的形式出现:

func greet(person: [String: String]) {
   guard let name = person["name"] else {
       return
   }

   print("Hello \(name)!")

   guard let location = person["location"] else {
       print("I hope the weather is nice near you.")
       return
   }

   print("I hope the weather is nice in \(location).")
}

greet(person: ["name": "John"])
// 输出“Hello John!”
// 输出“I hope the weather is nice near you.”
greet(person: ["name": "Jane", "location": "Cupertino"])
// 输出“Hello Jane!”
// 输出“I hope the weather is nice in Cupertino.” 

swift认为虽然上述功能使用 if 语句也可以实现,不过 guard...else... 可以提高代码的可读性。我对此持保留意见。

函数也是相当灵活

Swift中的函数也是十分灵活,其参数的定义和传递一开始或许让人觉得有些复杂,仔细研究会发现能够帮助我们构造十分优雅的API。而且和很多语言一样,Swift可以将函数作为参数传递,所以我们可以实现各种高阶函数。

函数参数定义和传递

func parseInt(v: String) -> Int? {
   return Int(v)
}

parseInt(v: "12") 

上面是个简单的函数定义,包含了函数的参数,函数返回类型等等。和其他语言(例如JavaScript)不同,我们一定要在函数调用时声明传递的参数名称 v ,这个对于某些API的设计来说可能有点过于冗余了,Swift提供了机制让我们省略它,它就是我们的好朋友 _

func parseInt(_ v: String) -> Int? {
   return Int(v)
}

parseInt("13") 

是不是简单明了了很多,我们还可以为这个函数添加进制转换

func parseInt(_ v: String, radix: Int = 10) -> Int? {
   return Int(v, radix: radix)
}

parseInt("13", radix: 8) 

这里我们默认的进制为10进制,这是函数的默认参数功能。其实 _ 这里表示的是忽略参数标签,我们也可以定义参数标签:

func parseInt(_ v: String, decimal radix: Int = 10) -> Int? {
   return Int(v, radix: radix)
}

parseInt("13", decimal: 8)

参数标签让函数调用方和函数定义方的表达可以有区别。

Swift还可以支持可变参数,关于可变参数我们先看个JavaScript的例子:

function sum(...numbers) {
 var s = 0;
 return numbers.reduce((r, el) => {
   return r + el
   }, s)
} 

Swift可以轻松实现:

func sum(_ numbers: Int...) -> Int {
   let s = 0
   return numbers.reduce(s) {r, el in r + el }
}
sum(1, 2, 3, 4) 

看起来简洁程度和JavaScript不相上下。这里其实可以看到,我们有个匿名的函数传递到reduce函数中,这里涉及到Swift的闭包技术,而且这里还是尾随闭包。

闭包

闭包在Swift中定义是可以捕获和存储其所在上下文中任意常量和变量的引用。有三种形式的闭包,我们先介绍第一种,嵌套函数形式的闭包,我们先以延迟计算为例:

func makeCost() -> (Int?) -> Int? {
   var args: [Int] = [];
   func cost(_ money: Int?) -> Int? {
       if (money != nil) {
           args.append(money!)
           return nil
       }
       var sum = 0
       for m in args {
           sum += m
       }
       return sum
   }
   return cost
}

let cost = makeCost()

cost(10) // nil
cost(20) // nil
cost(30) // nill
cost(nil) // 60 

这里 makeCost 内部的嵌套函数 cost 捕获了makeCost中的args变量,而全局变量cost也就形成了闭包。

我们之前写 sum 函数中 reduce 函数的用法是另外一种闭包形式,其实reduce函数还可以这样写:

numbers.reduce(s, { (r: Int, el: Int) -> Int in
   return r + el
}) 

这是闭包表达式,我们还可以进一步简化

numbers.reduce(s, { r, el in
   return r + el
}) 

Swift可以自行推断类型,所以可以全部去除。和JavaScript一样我们也可以去除return,隐式返回

numbers.reduce(s, { r, el in r + el }) 

我们可以更加简洁

numbers.reduce(s) { r, el in r + el } 

这个就是尾随闭包,如果函数的最后一个参数是闭包,那么我们可以使用这个形式,这样做的好处是,如果闭包中的代码比较复杂, 这样会比较优雅,结构更加清晰。

其实我们还可以更加"猥琐":

numbers.reduce(s){ $0 + $1 } 

可以用$0和$1指定r及el参数

More And More:

numbers.reduce(s, +) 

哈哈,这样的简洁JavaScript可是望尘莫及啊!

关于闭包其实还有逃逸闭包和非逃逸闭包的区别,这部分我打算放到学习自动引用计数的时候再说明。

今天的学习先到这里!!