Swift基础知识

变量和常量

任何 Swift 中的变量要么不变的,要么是可变的。这句话可不要和 Int、 Float 这些变量类型混淆。变量和常量仅仅是一种视角来描绘持有的值是可修改的(即可变性),亦或是不能修改的(即不可变性)。
要定义一个常量,使用 let 关键字。举例来说:

let name = "Jameson"````
如果你想改变 name 的值,会发现没有办法做到,然后 Swift 在编译时会抛出一个错误。
```
let name = "Jameson"
name = "Bob"
error: cannot assign to value: 'name' is a 'let' constant
name = "Bob"
~~~~ ^````
要解决它,我们可以使用 var 关键字,来定义一个可以修改的变量:

var name = "Jameson"
name = "Bob"````
这次代码就不会报错了。
一般来说,你应该默认去使用 let 关键字,除非知道需要使用到 var 关键字。这样的方式将会从根本上增加代码的安全性。如果当你定义了常量,之后要去修改它时,你会得到错误提示,可以到那个时候再决定是不是应该使用 var 关键字。或者如错误是给你的提示,应该重新去思考当前的逻辑流。一般来说,不可变性优于可变性,它可以帮助开发者少犯错误,并且更容易编写代码。

分号

在Swift 并不强制要求你在每条语句的结尾处使用分号( ; ),当然,你也可以按照 你自己的习惯添加分号。有一种情况下必须要用分号,即你打算在同一行内写多条独立的语句:

let cat = "?"; print(cat) 
// 输出 "?"

注释

//单行注释
/*
多行注释
*/````
####类型推断
在Swift中,可以不用显式的制定数据类型,swift能够自行推测出数据类型

####断言
断言在调试中非常有用,就像别的语言中的断点调试。Swift通过一个全局函数assert来实现。它有两个参数:一个表达式,一条信息,若表达式的结果是false就会中断程序并打印那条信息。
```
var age = 34
assert(age<18, "如果你小于18岁你就不是大叔了")
```
####基础类型
在 Swift 中,一个类型被声明的写法是通过声明一个变量,然后紧跟一个冒号,然后是类型名称。例如我们声明一个整型,在 Swift 中类型是 Int ,那么你可以如下写法:

let age: Int = 5````
相似的,你可以声明一个字符串类型:

let name: String = "Jameson"````
Swift 支持类型推断,可以不写具体的类型信息。然后让编译器根据它的初始值来推断它是什么类型的。

let age = 5
let name = "Jameson"````
age 和 name 的类型仍然是 Int 和 String ,但是这次我们跳过了类型声明,因为很显然,5 是 Int 类型,而 “Jameson” 是一个字符串。
记住, let 关键字仅仅使值变得不可变。如果我们预测这个 age 的值是可变的,而 name 不是可变的,那么我们应该这么写:

var age = 5
let name = "Jameson"````
现在如果要更新 age 的值,可以这么做:

var age = 5
let name = "Jameson"
age = 25
print(age)````

布尔值

Swift的布尔值为Bool,它有两个布尔常量--true和false。

浮点数

Swift有两种浮点数类型,64位浮点数的Double和32位的浮点数Float。

数值型类型转换

let heigth = 1.73 //
let iHeight = Int(heigth) //把height转成Int类型
let fHeight = Float(heigth) //把height转成Float类型
let dHeight = Double(heigth) //把height转成Double类型

使用字符串

用 print 打印命令或者 String 的字符串总是很方便。例如,我想要打印一个包含变量 age 和变量 name 的语句,可以在两个 String 变量之间用 + 操作符。

let age = "15"
let name = "Robb"
 
let sentence = name + " is " + age
print(sentence)````
打印结果为:Robb is 15
改用另外一个方式来拼接 String,可以不使用 + 操作符,而是在将每一个变量放进一组括号中,并在变量前使用 \ 反斜杠。

let sentence = "(name) is (age)"
print(sentence)
Robb is 15````
现在也可以看到同样的效果,但是它更容易阅读和组合。
也许你注意到了, age 现在是一个 String 类型因为它现在是 “15” 而不是 15,没有了引号。这是因为如果一个字符串和一个整型组合, Int 类型将不会自动转型为 String 类型,这在组合前是非常重要的一步。
这样的话,以下代码会产生错误。

let age = 15
let name = "Robb"
 
let sentence = name + " is " + age
print(sentence)````

Error: Binary operator '+' cannot be applied to operands
of type 'String' and 'Int'````
因此我们所要做的就是将 age 变成一个 String 类型的。可以通过强制转型来做到,使用 String 的初始化方法,传入一个 Int 类型的值作为参数值。

let age = 15
let name = "Robb"
 
let stringAge = String(age)
 
let sentence = name + " is " + stringAge
print(sentence)````
打印结果为:Robb is 15
我们创建了一个新的变量叫做 stringAge 。然后使用了类型转换,因为字符串插值操作会单独的分析每一个表达式,同样获取到圆括号内的内容。

let age = 15
let name = "Robb"

let sentence = name + " is " + String(age)
print(sentence)
print("(name) enjoys being (String(age))")````
打印结果为:
Robb is 15
Robb enjoys being15

可选类型

Swift 中有可选类型的概念。一个可选类型是一个可以为 nil、null 或者是没有被设置值的变量。一般来说,你可以认为大部分其他编程语言的任何变量都是一个可选类型。一个变量的可选性通过在类型声明时的类型名称后面加上问号符号 ? 来声明。 因此继续上面的例子,我们知道 age 和 name 总是会被设置,于是我们也许该添加另外一个可能为 nil 的变量。我们来拿 favoriteColor 来做一个例子。许多人都会有最爱的颜色,但可能对一部分人来说却没有,或者我们不知道别人的那些数据。因此我们会把它声明为可选类型,并且不对它进行赋值。

var favoriteColor: String?````
在对可选类型的声明中如果不进行赋值,那么它就是 nil 的。你可以使用 print 函数来对它进行打印来证实这个观点。

var favoriteColor: String?
print(favoriteColor)````
打印结果为:nil
我们之后将对 favoriteColor 进行赋值,然后发现它不再是 nil 的。

var favoriteColor: String?
favoriteColor = "Blue"
print(favoriteColor)
Optional("Blue")````
我们发现结果不是 "Blue" ,而是 Optional("Blue") 。那是因为实际值仍然包裹在可选类型之中。
你可以认为可选类型就像一个生日礼物,像礼物盒外面那层精美的包装纸,拆开他们之后,也许里面什么都没有。这对某些过生日的人真是个残忍的礼物,不过这确实真的会发生。也许礼物盒中确实会有真的礼物,可也得拆开并且实际去看才知道,现在它只是一个没有被拆开,躺在我们手中的一个盒子。
如果我们想知道里面是什么,需要马上拆开礼物,对可选类型来说也是一样,当传递和使用它们时,实际我们只是在和一个也许有值的容器在打交道。就像礼物一样,可选类型在被使用之前必须被解包。
Swift 中声明一个可选类型可以不赋值,编译也会通过。但是如果我们声明这些变量时不加上可选类型的符号,那么就会报错。

var favoriteColor = "Blue"
favoriteColor = nil````

error: nil cannot be assigned to type 'String'````
同样,非可选类型在声明的时候也不能被赋值为 nil。
```
var favoriteColor: String```
```
error: variables must have an initial value```
####nil
你可以给可选变量赋值为 nil 来表示它没有值:

var serverResponseCode: Int? = 404
// serverResponseCode 包含一个可选的 Int 值 404 serverResponseCode = nil
// serverResponseCode 现在不包含值````
注意:nil 不能用于非可选的常量和变量。如果你的代码中有常量或者变量需要处理值缺失的情况,请把它们声明成对应的可选类型。
如果你声明一个可选常量或者变量但是没有赋值,它们会自动被设置为 nil :

var surveyAnswer: String?
// surveyAnswer 被自动设置为 nil

注意:Swift 的 nil 和 Objective-C 中的 nil 并不一样。在 Objective-C 中, nil 是一个指向不存在对象的指 针。在 Swift 中, nil 不是指针——它是一个确定的值,用来表示值缺失。任何类型的可选状态都可以被设 置为 nil ,不只是对象类型。

解包

我们现在知道可选类型是什么了,它们可以使变量可以为空,也知道与其说它们是值不如说是一个容器。因此,在项目中要访问可选类型中的内容时,我们该怎么做?
第一,最普遍的方式是使用可选类型绑定,在可选绑定中,你可以在一个 if 语句中把可选类型的值赋给一个新的值。如果可选类型包含一个值,那个新的变量就会被成功设置,并且跟随 if 语句的代码闭包也会成功执行。
来看例子,这里将声明两个可选类型,一个叫做 favoriteAnimal ,它被设置值为 Fox ,而另外一个是 favoriteSong 我们并没有对它进行赋值。

var favoriteAnimal: String?
var favoriteSong: String?
 
favoriteAnimal = "Fox"```
现在我们使用可选绑定来看一看编程变量是否都有值,我们可以打印出包含它们值的语句。首先先来检查一下 favoriteAnimal。

if let unwrappedFavoriteAnimal = favoriteAnimal {
print("Favorite animal is: " + unwrappedFavoriteAnimal)
}
//打印结果为:Favorite animal is: Fox````
当没有被设置值时,仅仅会触发 else 语句,或者如果连 else 语句都没有,那么什么都不会触发。

if let unwrappedFavoriteSong = favoriteSong {
    print("Favorite song is: " + unwrappedFavoriteSong)
}
else {
    print("I don't know what your favorite song is!")
}
//打印结果为:I don't know what your favorite song is!````
如果我们要解包多个可选类型,并且对它们进行逻辑处理,首先要检查它们:

var favoriteAnimal: String?
var favoriteSong: String?

favoriteAnimal = "Fox"
favoriteSong = "Shake it Off"

if let unwrappedFavoriteSong = favoriteSong {
if let unwrappedFavoriteAnimal = favoriteAnimal {
print(unwrappedFavoriteSong + " " + unwrappedFavoriteAnimal)
}
}````
这看上去非常杂乱,因此 Swift 提供一种简便方式来一次解包多个变量:

var favoriteAnimal: String?
var favoriteSong: String?
 
favoriteAnimal = "Fox"
favoriteSong = "Shake it Off"
 
if let unwrappedFavoriteSong = favoriteSong,
    let unwrappedFavoriteAnimal = favoriteAnimal {
    print(unwrappedFavoriteSong + " " + unwrappedFavoriteAnimal)
}````
####集合类
Swift 有好几种集合类型,最常用的是数组、集合、字典。
**数组**
我们首先来看一下数组的例子。

let starks: [String] = ["Eddard", "Catelyn", "Robb", "Sansa"]````
这里我们定义了一个基本的 Array 类型,它是字符串数组类型 [String]。
这个方括号暗示了它是一个存放字符串对象的数组,而不是一个字符串类型。一般来说,Swift 可以通过检测所赋的初值进行类型推断。

let starks = ["Robb", "Sansa", "Arya", "Jon"]````
我们可以有多种方式访问数组中的元素,比如通过 Int 类型的下标,或者调用各种集合类型的方法。

let starks = ["Robb", "Sansa", "Arya", "Jon"]
print( starks[0] )
print( starks[2] )
print( starks.first! )````
打印结果为:
Robb
Arya
Robb
你应该发现数组是以 0 为下标开始的,因此数组中的第一个元素 "Robb" 可以通过 stack[0] 来访问。
另外,可能你会发现使用 first 方法返回的是一个可选值。而下标访问器返回的并不是一个可选值。如果访问数组中没有出现的下标,程序将会在运行时报错。因此在通过下标访问时检查数组的长度:

if starks.count >= 4 {
    print( starks[3] )
}````
有几种方式可以自动的检查这个类型,但是因为一些性能原因它不会默认去做。
**哈希类型/字典**
字典可以存储键值对,键的典型类型是字符串类型,但它也可以是 Swift 中的其他各种类型。在下面这个例子中,我们会创建一个基本字典,以字符串为键,整型为值。
```
let ages = ["Robb": 15, "Sansa": 12, "Arya": 10, "Jon": 15]```
我们可以访问这些值通过 String 的键
```
print( ages["Arya"]! )
print( ages["Jon"]! )````
打印结果为:10  15
要注意的是,我们解包这些值只是因为它们是可选值,它们有可能为 nil
使用可选绑定来解包字典中的值是较安全的,特别是你认为这些值很有可能为 nil 时

if let aryasAge = ages["Arya"] {
print("Arya is (aryasAge) years old")
}````
打印结果为:Arya is 10 years old
我们也可以把数组存储在字典中,或者把字典存储在数组中,或者把他们混合使用。

let families = [
    "Stark": ["Robb": 15, "Sansa": 12, "Arya": 10, "Jon": 15],
    "Baratheon": ["Joffrey": 13, "Tommen": 8]
]
let tommensAge = families["Baratheon"]!["Tommen"]!
print("Tommen is \(tommensAge) years old")````
打印结果为:Tommen is 8 years old
这个 houses 的类型将会是 [String: [String: Int]]
另外一个角度也可以说,这是一个字符串为键,以 [String: Int] 为值的一个字典。
**集合**
Swift3 中的集合和数组很相似,但集合的值是唯一的和无序的。
初始化一个集合看起来就像初始化一个数组,唯一不同的是类型:

let colors: Set<String> = ["Blue", "Red", "Orange", "Blue"]````
代码创建了一个字符串的集合。大于和小于符号 "<"">" 暗示 Swift 中的泛型类型,你可能注意到了 "Blue" 在列表中出现了两次,但是如果我们把颜色打印出来,马上就会发现:

let colors: Set<String> = ["Blue", "Red", "Orange", "Blue"]
print(colors)
///打印结果:["Orange", "Red", "Blue"]````
你也许还注意到了顺序也不一致了,因为集合不会维持特定的顺序。
我们无法像访问数组下标一样的方式去访问集合。但是可以用集合中内置的方法来增加或者删除元素,可以通过 contains 方法来查看是否集合中包含了该元素。

var colors: Set<String> = ["Blue", "Red", "Orange", "Blue"]
colors.insert("Black")
colors.insert("Black")
colors.remove("Red")
print(colors)
print(colors.contains("Black"))
print(colors.contains("Red"))
["Black", "Orange", "Blue"]
//true
//false````
构造集合对象最常见的方式就是罗列哪些元素应该纳入列表,哪些元素应该被排除。
这里还有许多方法我还没有提到,我建议你去阅读一下苹果的官方文档关于这三种集合类型,这样就会对它们更了解。
元组
元组并不是一种集合,而应该说是用一个标识符来表示多个不同变量。

let fullName = ("Jameson", "Quave")````
(String, String) 是一个元组类型,我们可以使用点语法来访问每一个元组的成员,看看下面的情况:

let fullName = ("Jameson", "Quave")
print(fullName.1)
print(fullName.0)
//Quave
//Jameson````
元组也可以用一个新的多个变量名来构造:

let (first, last) = ("Jameson", "Quave")
print(first)
//Jameson````
由于我们没有用到 last name,可以忽略那个值通过使用下划线 _ ,并且仍然构造 first name。

let (first, _) = ("Jameson", "Quave")
print(first)
Jameson````
当你在使用方法时想返回多个返回值时,元组会很有用。

控制流

Swift 的控制流比起其他语言要优雅,我们先从 if 和 else 语句这些基本层面着手:

if 10 > 5 {
  print("10 is greater than 5.")
}
else {
    print("10 is not greater than five.")
}
//10 is greater than 5```
你也可以用括号来包裹 if 语句的条件:

if (10 > 5) {
...
//Swift 也支持 switch 语句,在编译期的时候检查你是否已经覆盖了所有的可能条件,
//如果你没有覆盖所有的条件,你得加上 defualt:case 来处理一些没有考虑到的情况:
let name = "Jameson"
switch(name) {
case "Joe":
print("Name is Joe")
case "Jameson":
print("This is Jameson")
default:
print("I don't know of this person!")
}
//This is Jameson```
由于此处 name 的值是 "This is Jameson"。我们匹配到了第二个条件,然后执行下面这行。
print("This is Jameson")
如果我们把名称设置为一些之前没有出现在列举情况的东西时,比如 "Jason" ,switch 将会自动落入默认的情况:

let name = "Jason"
switch(name) {
case "Joe":
  print("Name is Joe")
case "Jameson":
  print("This is Jameson")
default:
  print("I don't know of this person!")
}
//I don't know of this person!```
####循环和集合类型
Swift3 不再支持你过去所使用的 C 风格的循环,取而代之的是使用枚举和 for-each 风格的循环,语法是` for element in array`

let names = ["Robb", "Sansa", "Arya", "Jon"]

for name in names {
print("Name: (name)")
}
Name: Robb
Name: Sansa
Name: Arya
Name: Jon```
如果你想要循环整个数组,这个写法就很棒,没有 C 风格的数组,如果我们循环遍历一系列数字呢?Swift 中的 Range 和 Stride 给出了答案,如果想要打印到 10 里面3的倍数,可以使用 Range 通过使用语法 1…10 表示从 1 到 10 。然后我们打印每一个数字,那些数字都被 % 符号除以 3 ,并且检查它们的余数是不是都是0。

for i in 1...10 {
    if i % 3 == 0 {
        print(i)
    }
}```
输出结果为:3 6 9
另外一种方式是通过 stride 每隔三个元素访问一次。stride 可以用很多方法来创建,但是最常见的是 stride(from: , to:, by:),from value 就是跨步访问的起初值,然后 by 是每隔多少跨步值才能访问到 to 值。听起来有点绕,让我们来看实际的代码:

let byThrees = stride(from: 3, to: 10, by: 3)
for n in byThrees {
print(n)
}```
输出结果为:3 6 9
从英语来看十分好读,你也可以说你从 3 数到 10 每隔3个数。这里我们创造 stride 并且用一个变量 byThrees 来存储他们的值,但是也可以直接在循环中使用它们。

for n in stride(from: 3, to: 10, by: 3) {
    print(n)
}````
//输出结果为3 6 9
集合都有一个 indices 属性用于循环中使用,它会返回一个集合的下标数组,非常适合访问或者过滤集合中某些元素的情况。这里回到我们的名称集合的例子,如果想要前三个名称,可以这样写:

let names = ["Robb", "Sansa", "Arya", "Jon"]
for nameIndex in names.indices {
if(nameIndex < 3) {
print(names[nameIndex])
}
}
Robb, Sansa, Arya````
在集合中还有枚举的方法,它允许你通过遍历下标和值:

let names = ["Robb", "Sansa", "Arya", "Jon"]
for (index, name) in names.enumerated() {
    print("\(index): \(name)")
}
0: Robb
1: Sansa
2: Arya
3: Jon````
在 Swift3 中还有很多方式来遍历对象,但他们通常不是很常用。
也许你已经发现我们的循环中同时给两个变量赋值,index 和 name。它们被用逗号分隔并被括号括起来,表示我们从 enumerated() 返回的两个被命名的变量。
####函数和闭包
使用`func`来声明函数,使用名字和参数来调用函数。使用->来指定函数返回值的类型。 

//声明creat方法,传入personday两个参数,返回一个String类型的值
func creat(person: String, day: String) -> String {
return "Hello,(person),today is (day)."
}
//使用creat函数调用生成一个字符串,并赋值给result
let result = creat(person: "小王", day: "星期八")

默认情况下,函数使用他们的参数作为他们的参数的标签,在参数名称前可以自定义参数标签,或者使用\`_`表示不适用参数标签。

//声明函数
func creat1(_ person: String, day: String) -> String {
return "Hello,(person),today is (day)."
}
//调用方式
let result1 = creat1( "小王", day: "星期八")
print(result1);

使用元组来让一个函数返回多个值,元组的元素可以用名称和数字来表示。

//声明一个函数
func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) {
var min = scores[0]
var max = scores[0]
var sum = 0
for score in scores {
if score > max {
max = score
} else if score < min {
min = score
}
sum += score
}
return (min, max, sum)
}
//调用函数
let statistics = calculateStatistics(scores:[5, 3, 100, 3, 9])
print(statistics.sum)
print(statistics.2)

函数可以嵌套。被嵌套的函数可以访问外侧函数的变量,你可以使用嵌套函数来重构一个太长或者太复杂的函
数。

func returnFifteen() -> Int {
var y = 10
func add() {
y += 5
}
add()
return y
}
returnFifteen()

函数是第一等类型,这意味着函数可以作为另一个函数的返回值。

func makeIncrementer() -> ((Int) -> Int) {
func addOne(number: Int) -> Int {
return 1 + number
}
return addOne
}
var increment = makeIncrementer()
increment(7)

函数也可以当做参数传入另一个函数。

func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
for item in list {
if condition(item) {
return true
} }
return false
}
func lessThanTen(number: Int) -> Bool {
return number < 10
}
var numbers = [20, 19, 7, 12]
hasAnyMatches(list: numbers, condition: lessThanTen)

函数实际上是一种特殊的闭包:它是一段能之后被调取的代码。闭包中的代码能访问闭包所建作用域中能得到的变 量和函数,即使闭包是在一个不同的作用域被执行的 - 你已经在嵌套函数例子中所看到。你可以使用 {} 来创建 一个匿名闭包。使用` in `将参数和返回值类型声明与闭包函数体进行分离。

numbers.map({
(number: Int) -> Int in
let result = 3 * number
return result
})````

对象和类

使用 class 和类名来创建一个类。类中属性的声明和常量、变量声明一样,唯一的区别就是它们的上下文是 类。同样,方法和函数声明也一样。

class Shape {
    var numberOfSides = 0
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

要创建一个类的实例,在类名后面加上括号。使用点语法来访问实例的属性和方法。

var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()

这个版本的 Shape 类缺少了一些重要的东西:一个构造函数来初始化类实例。使用 init 来创建一个构造器。

class NamedShape {
    var numberOfSides: Int = 0
    var name: String
    init(name: String) {
        self.name = name
   }
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    } 
}

注意 self被用来区别实例变量。当你创建实例的时候,像传入函数参数一样给类传入构造器的参数。每个属性都 需要赋值——无论是通过声明(就像 numberOfSides )还是通过构造器(就像 name )。
如果你需要在删除对象之前进行一些清理工作,使用 deinit创建一个析构函数。
子类的定义方法是在它们的类名后面加上父类的名字,用冒号分割。创建类的时候并不需要一个标准的根类,所以你可以忽略父类。
子类如果要重写父类的方法的话,需要用 override 标记——如果没有添加 override 就重写父类方法的话编译器 会报错。编译器同样会检测 override 标记的方法是否确实在父类中。

class Square: NamedShape {
    var sideLength: Double
    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 4
}
    func area() ->  Double {
        return sideLength * sideLength
}
    override func simpleDescription() -> String {
        return "A square with sides of length \(sideLength)."
} }
let test = Square(sideLength: 5.2, name: "my test square")
test.area()
test.simpleDescription()

除了储存简单的属性之外,属性可以有 getter 和 setter 。

class EquilateralTriangle: NamedShape {
    var sideLength: Double = 0.0
    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 3
}
var perimeter: Double {
        get {
            return 3.0 * sideLength
        }
        set {
            sideLength = newValue / 3.0
} }
    override func simpleDescription() -> String {
        return "An equilateral triagle with sides of length \(sideLength)."
} }
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
print(triangle.perimeter)
triangle.perimeter = 9.9
print(triangle.sideLength)

在 perimeter 的 setter 中,新值的名字是 newValue 。你可以在 set 之后显式的设置一个名字。 注意 EquilateralTriangle 类的构造器执行了三步:

  1. 设置子类声明的属性值
  2. 调用父类的构造器
  3. 改变父类定义的属性值。其他的工作比如调用方法getters 和 setters 也可以在这个阶段完成。

如果你不需要计算属性,但是仍然需要在设置一个新值之前或者之后运行代码,使用 willSet 和 didSet 。 比如,下面的类确保三角形的边长总是和正方形的边长相同。

class TriangleAndSquare {
    var triangle: EquilateralTriangle {
        willSet {
            square.sideLength = newValue.sideLength
} }
    var square: Square {
        willSet {
            triangle.sideLength = newValue.sideLength
        }
    }
    init(size: Double, name: String) {
        square = Square(sideLength: size, name: name)
        triangle = EquilateralTriangle(sideLength: size, name: name)
    }
}
var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
print(triangleAndSquare.square.sideLength)
print(triangleAndSquare.triangle.sideLength)
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
print(triangleAndSquare.triangle.sideLength)

处理变量的可选值时,你可以在操作(比如方法、属性和子脚本)之前加 ? 。如果 ? 之前的值是 nil , ? 后面 的东西都会被忽略,并且整个表达式返回 nil 。否则, ? 之后的东西都会被运行。在这两种情况下,整个表达式 的值也是一个可选值。

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
let sideLength = optionalSquare?.sideLength

枚举和结构体

使用 enum 来创建一个枚举。就像类和其他所有命名类型一样,枚举可以包含方法。

enum Rank: Int {
    case Ace = 1
    case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten
    case Jack, Queen, King
    func simpleDescription() -> String {
        switch self {
        case .Ace:
            return "ace"
        case .Jack:
            return "jack"
        case .Queen:
            return "queen"
        case .King:
            return "king"
        default:
            return String(self.rawValue)
        }
} }
let ace = Rank.Ace
let aceRawValue = ace.rawValue

默认情况下,Swift 按照从 0 开始每次加 1 的方式为原始值进行赋值,不过你可以通过显式赋值进行改变
使用init?(rawValue:)初始化构造器在原始值和枚举值之间进行转换。

if let convertedRank = Rank(rawValue: 3) {
    let threeDescription = convertedRank.simpleDescription()
}

枚举的成员值是实际值,并不是原始值的另一种表达方法。实际上,如果没有比较有意义的原始值,你就不需要
提供原始值。

enum Suit {
    case Spades, Hearts, Diamonds, Clubs
    func simpleDescription() -> String {
    switch self {
        case .Spades:
            return "spades"
        case .Hearts:
            return "hearts"
        case .Diamonds:
            return "diamonds"
        case .Clubs:
            return "clubs"
        }
} }
let hearts = Suit.Hearts
let heartsDescription = hearts.simpleDescription()

注意,有两种方式可以引用 Hearts 成员:给 hearts 常量赋时,枚举成员 Suit.Hearts 需要用全名来引用,因 为常量没有显式指定类型。在 switch 里,枚举成员使用缩写 .Hearts 来引用,因为 self 的值已经知道是一个 it 。已知变量类型的情况下你可以使用缩写。
一个枚举成员的实例可以有实例值。相同枚举成员的实例可以有不同的值。创建实例的时候传入值即可。实例值和原始值是不同的:枚举成员的原始值对于所有实例都是相同的,而且你是在定义枚举的时候设置原始值。

使用 struct 来创建一个结构体。结构体和类有很多相同的地方,比如方法和构造器。它们之间最大的一个区别就 是结构体是传值,类是传引用。

struct Card {
    var rank: Rank
    var suit: Suit
    func simpleDescription() -> String {
  return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
  }
 }
let threeOfSpades = Card(rank: .Three, suit: .Spades)
let threeOfSpadesDescription = threeOfSpades.simpleDescription()

协议和扩展

使用 protocol 来声明一个协议。

protocol ExampleProtocol {
    var simpleDescription: String { get }
    mutating func adjust()
}

类、枚举和结构体都可以实现协议。

class SimpleClass: ExampleProtocol {
    var simpleDescription: String = "A very simple class."
    var anotherProperty: Int = 69105
    func adjust() {
        simpleDescription += "  Now 100% adjusted."
    }
}
var a = SimpleClass()
a.adjust()
let aDescription = a.simpleDescription
struct SimpleStructure: ExampleProtocol {
    var simpleDescription: String = "A simple structure"
    mutating func adjust() {
        simpleDescription += " (adjusted)"
    }
}
var b = SimpleStructure()
b.adjust()
let bDescription = b.simpleDescription

注意声明 SimpleStructure 时候 mutating 关键字用来标记一个会修改结构体的方法。 SimpleClass 的声明不需要 标记任何方法,因为类中的方法通常可以修改类属性(类的性质)。
使用 extension 来为现有的类型添加功能,比如新的方法和计算属性。你可以使用扩展在别处修改定义,甚至是 从外部库或者框架引入的一个类型,使得这个类型遵循某个协议。

extension Int: ExampleProtocol {
    var simpleDescription: String {
        return "The number \(self)"
    }
    mutating func adjust() {
self += 42 }
}
print(7.simpleDescription)

你可以像使用其他命名类型一样使用协议名——例如,创建一个有不同类型但是都实现一个协议的对象集合。当
你处理类型是协议的值时,协议外定义的方法不可用。

let protocolValue: ExampleProtocol = a print(protocolValue.simpleDescription)
// print(protocolValue.anotherProperty) // 去掉注释可以看到错误

即使 protocolValue 变量运行时的类型是 simpleClass ,编译器会把它的类型当做 ExampleProtocol 。这表示你不 能调用类在它实现的协议之外实现的方法或者属性。

错误处理

使用采用 Error协议的类型来表示错误。

enum PrinterError: Error {
    case OutOfPaper
    case NoToner
    case OnFire 
}

使用throw来抛出一个错误并使用 throws 来表示一个可以抛出错误的函数。如果在函数中抛出一个错误,这个函 数会立刻返回并且调用该函数的代码会进行错误处理。

func send(job: Int, toPrinter printerName: String) throws -> String {
    if printerName == "Never Has Toner" {
        throw PrinterError.noToner
    }
    return "Job sent"
}

有多种方式可以用来进行错误处理。一种方式是使用 do-catch 。在 do 代码块中,使用 try 来标记可以抛出错误 的代码。在 catch 代码块中,除非你另外命名,否则错误会自动命名为 error 。

do {
    let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
    print(printerResponse)
} catch {
    print(error)
}

可以使用多个 catch 块来处理特定的错误。参照 switch 中的 case 风格来写 catch 。

do {
    let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")
    print(printerResponse)
} catch PrinterError.onFire {
    print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
    print("Printer error: \(printerError).")
} catch {
    print(error)
}

另一种处理错误的方式使用try? 将结果转换为可选的。如果函数抛出错误,该错误会被抛弃并且结果为nil。否则的话,结果会是一个包含函数返回值的可选值。

let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")

使用 defer 代码块来表示在函数返回前,函数中最后执行的代码。无论函数是否会抛出错误,这段代码都将执 行。使用defer,可以把函数调用之初就要执行的代码和函数调用结束时的扫尾代码写在一起,虽然这两者的执 行时机截然不同。

var fridgeIsOpen = false
let fridgeContent = ["milk", "eggs", "leftovers"]
func fridgeContains(_ food: String) -> Bool {
    fridgeIsOpen = true
    defer {
        fridgeIsOpen = false
    }
    let result = fridgeContent.contains(food)
    return result
}
fridgeContains("banana")
print(fridgeIsOpen)

泛型

在尖括号里写一个名字来创建一个泛型函数或者类型。

func repeatItem<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] {
    var result = [Item]()
    for _ in 0..<numberOfTimes {
        result.append(item)
    }
    return result
}
repeatItem(repeating: "knock", numberOfTimes:4)

你也可以创建泛型函数、方法、类、枚举和结构体。

// 重新实现 Swift 标准库中的可选类型 enum OptionalValue<Wrapped> {
case None
    case Some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .None
possibleInteger = .Some(100)

在类型名后面使用 where 来指定对类型的需求,比如,限定类型实现某一个协议,限定两个类型是相同的,或者 限定某个类必须有一个特定的父类。

func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Bool
    where T.Iterator.Element: Equatable, T.Iterator.Element == U.Iterator.Element {
        for lhsItem in lhs {
            for rhsItem in rhs {
                if lhsItem == rhsItem {
                    return true
} }
        }
        return false
}
anyCommonElements([1, 2, 3], [3])

<T: Equatable> <T> ... where T: Equatable>是等价的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容