第五章 流程控制与其他
本章介绍Swift语言剩余的其他方面;
1)Swift分支、循环与跳转流程控制结构的语法;
2)如何重写运算符以及如何创建自定义运算符;
3)Swift的隐私特性与内省特性;
4)以及用于引用类型内存管理的专用模式;
5.1 流程控制
计算机程序都有通过代码语句表示的执行路径:连续执行每一条语句;
流程控制用于让执行路径调到某些代码语句,或是重复执行一些代码语句;
通过测试条件的真值,程序可以确定如何继续;
基于条件测试的流程控制大体上可以分为以下两种类型;
1)分支
代码被划分为不同的区块,像分叉路一样,程序有几种可能进行下去的方式:条件真值用于确定哪一个代码区块会被真正执行;
2)循环
将代码块划分出来以重复执行:条件真值用于确定代码块是否应该执行,然后是否应该再次执行;每次重复都叫做一次迭代;一般来说,每次迭代时都会改变一些环境特性(比如,变量的值),这样重复就不是一样的了,而是整个任务处理中的连续阶段;
控制流程中的代码块(称为块)是由花括号包围的,这些花括号构成了一个作用域;
可以在里面声明新的局部变量,离开花括号之后,会自动消亡;
与C不同,在Swift中,条件不必放到圆括号中,不过花括号是不能省略的;此外,Swift还添加了一些专门的流程控制特性,帮助更好的使用Optional;
5.1.1 分支
Swift有两种形式的分支:if结构以及switch语句;此外本节还会介绍 条件求值,它是if结构的一种紧凑形式;
1. if结构
if condition{
statements
}
if...{ }else{ }
if...{ }else if...{ }else{ }
自定义嵌套作用域:
有时,当知道某个局部变量只需要在几行代码中存在时,你可能会自己定义一个作用域——自定义嵌套作用域;在作用域开头引入局部变量,在作用域结尾会自动销毁;
不过Swift不允许你使用空的花括号来这么做;而是用如下这种结构:
do {
}
2. 条件绑定
if后紧跟变量的声明和赋值,叫做条件绑定,形式如 if let/var name:Type = value ;
它实际上是条件展开Optional的缩写,赋的value值是个Optional,如果不是,编译器会报错:
1)如果Optional为nil,那么条件会失败,块也不会执行;
2)如果Optional不为nil,那么:
·Optional会被展开;
·展开的值会被赋值给声明的局部变量;
·块执行时局部变量会处于作用域中;
因此,条件绑定是将展开的Optional安全地传递给块的一种便捷方法,只有Optional可展开块才会执行;
条件绑定中的局部变量可以与外部作用域中的已知变量同名,他甚至可以与被展开的Optional同名;这样就不会无意中访问Optional;(我一般都会用一个简写)
如:(仅作为示例说明,类似的场景比较多)
if let prog = (notify.userinfo?["progress"] as? NSNumber)?.doubleValue{
self.progress = prog
}
当然,也可以进行条件绑定的嵌套:
if let u = notify.userinf{
if let prog:AnyObject = u["progress"]{
if let prog = prog as? NSNumber {
}
}
}
为了避免缩进,可以将连续的条件绑定放到一个列表中,中间用逗号隔开:
if let u = notify.userinf, prog:AnyObject = u["progress"] as? NSNumber {
//注意=号后边是一个整体
}
列表中的绑定甚至可以后跟一个where子句,将另一个条件放到一行当中;这个子句对已经绑定的局部变量做了进一步的限制;
现在,你也可以通过一些列的guard语句来表示这个可选链:
guard语句:表示满足条件则继续;
guard...else{ }
对应可选绑定的话,则为 guard if let/var name:Type = value else{ },value可解包,name有值,则继续,否则执行else中内容,一般是return;
3. Switch语句
Switch是一种更为简洁的if...else if...else结构编写方式(C中Switch有个隐藏的陷阱,Swift消除了这个陷阱,并增加了功能和灵活性);
在switch中,条件位于不同可能值与单个值的比较(叫做标记)中,叫做case;
case比较是按照顺序执行的,如果单个case比较成功,那么case的代码就会执行,整个switch语句将会退出;
switch tag {//tag就是标记
case pattern1:
statements
case pattern1:
statements
default:
statements
}
在Swift中,Switch语句必须是完备的,这意味着标记的每个可能值都必须要被case覆盖到,常见的扫尾case是使用一个default 的case;
每个case至少要包含一行代码,最少的单行case代码只包含关键字break;这种情况下,break表示一个占位符,什么都不做;
很多时候,switch语句会包含一个default(或其他扫尾case),他只包含了关键字break;这样就可以穷尽标记的所有可能值;
Swift中,case值实际上是一个叫做模式的特殊表达式,该模式会通过“隐私的模式匹配运算符~=”与标记值进行比较;
诸多使用场景:
·模式还可以包含一个下划线(_)来表示所有其他值;它是扫尾case的一种替换形式;
var i = 4
switch i {
case 1:
print("You have 1 thingy!")
case _:
print("You have many thingys!")//You have many thingys!
}
·模式可以包含局部变量的声明(无条件绑定)来表示所有值,并将实际值作为该局部变量的值;它是扫尾case的另一种替换方案:
switch i {
case 1:
print("")
case let n:
print("\(n)")//4
}
·如果标记是个Comparable,那么case还可以包含Range;比较时会向Range发送contains消息;
switch i {
case 1:
print("")
case 2...10:
print("\(i)")//4
default:
break
}
·如果标记是一个Optional,那么case可以将其与nil进行比较;
这样,安全展开Optional的一种方式就是先将其与nil进行比较,并在随后的case中将其展开;
let j:Int? = 5
switch j {
case nil:
break
default:
switch j! {
case 1:
print("")
case let n:
print("\(n)")//5
}
}
//也可以将?追加到case模式上,安全地展开一个Optional;
switch j {
case 1?:
print("1")
case let n?:
print("\(n)")//5
case nil:
break
}
·如果标记是一个bool值,那么case就可以将其条件进行比较了;
将true作为标记,就可以通过case测试任何条件;这样switch就变成了扩展的if...else if结构的替代者;
let x = 0
let y = 1
switch true {
case x < y:
print("x < y")//x < y
case x == y:
print("x == y")
default:
print("x > y")
}
·模式还可以包含where子句来添加条件,从而限制case的真值;他常常会与绑定搭配使用,但这并非强制要求;条件可以引用绑定中声明的变量;
case let j where j > 0:
·模式可以通过is运算符来判断标记的类型;
case is NoisyDog:
·模式可以通过as(不是as?)运算符进行类型转换;
case let d as NoisyDog: //表示可以被转换为NoisyDog的所有case真值;
·在与特定的值进行比较时,你还可以使用as(不是as!)运算符根据情况对标记(注意是对标记)进行向下类型转换;
case 0 as Int: //对应可以将tag转换为Int类型之后,值是0的情况;
·你可以将标记表示为元组,同时将相应的比较也包装到元组中,这样就可以同时进行多个比较了;
只有当比较元组与相应的标记元组中的每一项比较都通过,这个case 才算通过;
case let (size as Int , desc as String):
·如果标记是个枚举,那么case就可以是枚举的case;
如果case覆盖了所有的情况,那么就可以不需要扫尾case了;
关于枚举case中抽取关联值:
1)Switch语句提供了从枚举case中抽取出关联值的方式;
enum ErrorA {
case Number(Int)
case Message(String)
case Fatal
}
let error = ErrorA.Number(7)
switch error {
case .Number(let n):
print("\(n)")//7
case let .Message(s) where s.characters.count > 0:
print(s)
default:
print("Fatal")
break
}
回忆一下:关联值实际上是一个元组;匹配case名后的模式元组会应用到关联值上;如果模式是个绑定变量,那么他会捕捉到关联的值;
let/var可以位于圆括号中,或是在case 关键字后;如上面代码演示的;
如果let(或var)位于case关键字之后,那么就可以添加一个where子句;
2)如果不想抽取错误号,只想进行匹配,那就可以在圆括号中使用另外一种模式;
switch error {
case .Number(1..<Int.max):
print("正数")//正数
default:
print("其他")
}
我们可以通过轻量级的if case结构在条件中使用与switch语句的case相同模式的语法,来进行关联值的抽取;if case后面会跟着一个等号和标记;
if case let .Number(n) = error {
print(n)//7
}
甚至还可以在switch case中附加一个where子句,和之前介绍的一样;
组合case:
1)要想将case测试组合起来(使用隐式的逻辑或),可以用逗号将其分隔开;
i = 5
switch i {
case 1,3,5,7:
print(i)//5
default:
print("no pattern")
}
switch i {
case is Int , is Double:
print("number")//number
default:
print("other")
}
不过你不能使用逗号组合声明绑定变量的模式,因为这种情况下,对变量的赋值是不确定的;
2)组合case的另一种方式是通过fallthrough语句从一个case落到下一个case上;
注意,fallthrough会跳过下一个case的测试;他会直接开始执行下一个case的代码;因此,下一个case不能声明任何绑定变量,因为无法对变量赋值;
4. 条件求值
使用分支语句作为变量值会更好些,比如将变量赋值语句等号后直接跟着一个分支结构——有些语言允许这样,但是Swift不行,不过,我们可以使用定义与匿名函数调用:
enum SortA :String{//注意这种类型的枚举 是不能进行关联值提取的,它的关联值实际上是一个空元组“()”;他持有的只是一个原生值
case Al
case Bl
}
let sortA = SortA.Al
print(sortA.rawValue)//Al
enum SortB {
case Al(Int)
case Bl(String)
}
let sortB = SortB.Al(6)
let title:String = {
switch sortB {
case let .Al(s):
return "\(s)"//6
case let .Bl(s):
return s
}
}()
print(title)
这种在进行分支判断然后赋值的场景应该会很有用;
三元运算符:
有时,值通过一个二路条件才能确定下来,Swift提供了一个类似C语言的三元运算符,形如 condition ? exp1 : exp2
nil合并运算符:
如果被测试的Optional为nil,那就会使用替代值;否则会展开Optional,并使用被包装的值,即??运算符;叫做nil合并运算符;
回忆第四章的示例:
let arr :[Int?] = [1,nil,2,nil,4]
let arr2:[AnyObject] = arr.map{if $0 == nil {return NSNull()} else { return $0! as AnyObject }}
print(arr2)//[1, <null>, 2, <null>, 4]
//使用三元运算符
let arr3:[AnyObject] = arr.map{$0 == nil ? NSNull() : $0! as AnyObject}
print(arr3)//[1, <null>, 2, <null>, 4]
//使用nil合并运算符
let arr4 = arr.map{ $0 ?? -1}
print(arr4)//[1, -1, 2, -1, 4]
还可以将使用??的表达式链接起来:
let somenumber = i1 as? Int ?? i2 as? Int ?? 0
上述代码尝试将i1类型转换为Int并使用该Int;如果失败,那么他会尝试将i2类型转换为Int并使用该Int;如果这也失败就会放弃并使用0.
5.1.2 循环
循环的目的在于重复一个代码块的执行,并且在每次迭代时会有一些差别;这种差别通常还作为循环停止的信号;
Swift提供两种循环结构:while循环与for循环;
1. while循环
while循环有两种形式:
while condition {
statements
}
repeat{
statements
} while conditon
第二种代码块至少运行一次;
一般来说,块中的代码会修改一些东西,这回影响环境与条件,最终让循环结束;
使用:
1)while let:
while循环的第一种形式的条件可以是Optional的条件绑定,知道Optional为nil;
如:while let p = self.movenda.popLast(){}
2)while循环的一种常见用法是沿着层次结构向上或向下遍历;条件可以设置在where子句中;
如:
var v :UIView = textField
while let vv = v.superview where !(vv is UITableViewCell){v = vv}
if let c = v.superview as? UITableViewCell{...}
这段代码是安全的,while let 进行可选绑定,退出条件既可以是vv = nil(即没找到),也可以是where子句不满足(找到了);之后再做可选的类型装换,如果找到了,那么c就是我们最终找到的父视图;
3)while case:
类似if case结构,在while case 中也可以使用switch case模式;
enum ErrorH {
case Message(String)
case Number(Int)
case Fatal
}
let arrH:[ErrorH] = [.Message("hahaha"),.Message("flower"),.Number(5),.Message("flower1"),.Fatal]
var ids:Int = 0
while case let .Message(str) = arrH[ids] {
print(str)//hahaha flower
ids = ids + 1
}
该循环会从开始位置提取出与.Message关联的字符串值,遇到无法提取的则循环终止;
2. for循环
for循环的两种形式:
for variable in sequence {
statements
}
for before-all ; condition ; after-each {
statements
}
OC中的for-in要求,一个类遵循了NSFastEnumeration协议;
Swift中,要求一个类型使用了SequenceType协议;
在for-in结构中,变量会在每次迭代中隐式通过let进行声明;因此在默认情况下它是不可变的(如果需要对变量进行赋值,那么请写成for var);该变量对于块来说也是局部变量;
SequenceType又一个makeIterator方法,他会生成一个‘迭代器’对象,这个迭代器对象本身又一个mutating的next方法,该方法会返回序列中的下一个对象,并被包装到Optional中,如果没有下一个对象,那么他会返回nil;
for-in循环过程就在不断调用迭代器的next方法,直到返回nil:
var g = (1...5).makeIterator()
while let i = g.next() {
print(i)
}
使用:
1)遇到来自OC的数组,需要从AnyObject进行向下类型装换:
如: for boy in p.boys() as! [String] {}
2)序列的enumerate方法会生成一个元组序列,并在原始序列的每个元素前加上其索引号:
let titlesA = ["a","b","c","d"]
for (ids,value) in titlesA.enumerated() {
print("\(ids)" + value)
}
输出如下:
0a
1b
2c
3d
3)附加where子句:进一步进行值的筛选;
4)for case 模式匹配:
for case let .Number(n) in arrH {
print(n)//
}
5)可以通过调用stride方法来生成序列,它是strideable协议的一个实例方法;并且可被数字类型与可增加及减少的所有类型所使用,他拥有两种形式:
stride(from: , through: , by: ) 包括最终值
stride(from: , to: , by: ) 不包括最终值,by指的是间隔
for item in stride(from: 1, to: 5, by: 2){
print(item)//1 3
}
for item in stride(from: 1, through: 5, by: 2){
print(item)//1 3 5
}
6)zip函数:同时遍历两个序列,生成一个Zip2结构体,本身也是一个序列;值是原来两个序列中相应元素所构成的元组;较长序列的元素会被忽略;
let arrX = [1,3,5,7]
let arrY = ["a","b","c","d","e"]
for (item1 , item2) in zip(arrX, arrY) {
print("\(item1)" + item2)//1a 3b 5c 7d
}
第二种形式的for循环:
来源于C的循环,主要用于增加或减少计数器值;
before-all语句会在进入for循环时执行一次,通常用于计数器的初始化;
接下来是测试条件,为true则代码会执行;条件通常用来测试计数器是否达到了某个限值;
接下来就是执行after-each语句,通常用于增加或减少计数器值;接下来会再次测试条件;