好奇心驱使,最近在学习Swift这门语言。第一次接触了解概况时觉得它像Javascript,Python等脚本语言,随着深入学习让我领略到了强类型语言的严谨慎密。

此篇文章记录在阅读Swift官方教程中文版中的重点摘要笔记。

基础语法

变量和常量

Swift中用let声明常量,用var声明变量。

1
2
let maxNumber = 10;
var currentNumber = 0;

常量设置后就不可以修改。

数据类型

Swift中常用数据类型有:IntFloatDoubleStringBoolTuplesArraysSetsDictionary

数值

Swift中Int提供了8(Int8、UInt8)、16(Int16、UInt16)、32(Int32、UInt32)、64(Int64、UInt64)位的有符号和无符号(U)整数类型。

整数字面量可以被写作:

  • 一个十进制数,没有前缀
  • 一个二进制数,前缀是0b
  • 一个八进制数,前缀是0o
  • 一个十六进制数,前缀是0x
1
2
3
4
let decimalInteger = 17
let binaryInteger = 0b10001 // 二进制的17
let octalInteger = 0o21 // 八进制的17
let hexadecimalInteger = 0x11 // 十六进制的17

在声明变量时你可以写上数据类型,也可以不写。Swift会根据你赋值推断该变量的类型。

1
2
3
var oneMillion: Int = 1_000_000 // 一百万
var name: String = "Fynn"
var age = 28

浮点数

Double表示64位浮点数,它的精确度更高至少有15位数字。Float表示32位浮点数,只有6位数字。推荐有限选择Double

整数和浮点数转换必须显式指定类型:

1
2
3
let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three)+pointOneFourOneFiveNine

字符串

字符串是一组有序的Character类型的值的集合,通过String类型来表示。

多行字符串

1
2
3
4
5
6
7
let quotation = """
The White Rabbit put on his spectacles. "Where shall I begin,
please your Majesty?" he asked.

"Begin at the beginning," the King said gravely, "and go on
till you come to the end; then stop."
"""

多行字符串有一对三个双引号包裹着的。引号内没有换行符,如果你想在多行字符串字面量中出现换行符的话,可以在行尾写一个反斜杠()作为续行符。

字符串字面量可以包含一下特殊字符:

  • 转义字符\0(空字符)、\(反斜线)、\t(水平制表符)、\n(换行符)、\r(回车符)、"(双引号)、'(单引号)。
  • Unicode 标量,写成\u{n}(u为小写),其中n为任意一到八位十六进制数且可用的 Unicode 位码。

遍历字符串

可以通过for-in循环来遍历字符串,获取字符串中的每一个字符的值:

1
2
3
4
5
6
7
8
for character in "Mouse" {
print(character)
}
// M
// o
// u
// s
// e

可以通过+=将两个字符串相加,也可以通过append()方法将一个字符串附加到另一个字符串变量尾部。

1
2
3
4
var str1 = "hello"
var str2 = "world"
str1 += str1
str1.append("!")

注意: 不能将一个字符串或一个字符添加到一个已经存在的字符变量上。因为字符变量只能包含一个字符。

可以向字符串中传入变量,语法是:\(variable)

1
2
3
let multiplier = 3
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
// message 是 "3 times 2.5 is 7.5"

Unicode

Unicode是一个国际标准,用于文本的编码和表示。它使您可以用标准格式表示来自任意语言几乎所有的字符,并能够对文本文件或网页这样的外部资源中的字符进行读写操作。 Swift 的StringCharacter类型是完全兼容 Unicode 标准的。

Swift 的String类型是基于 Unicode 标量 建立的。 Unicode 标量是对应字符或者修饰符的唯一的21位数字,例如U+0061表示小写的拉丁字母(LATIN SMALL LETTER A)(“a”),U+1F425表示小鸡表情(FRONT-FACING BABY CHICK) (“🐥”)。

Swift中一个Unicode码位表示一个字符。

字符串索引

每一个String值都有一个关联的索引(index)类型,如果需要获取一个字符串中指定位置的字符需要先获取到该字符的index索引值。

获取index索引值方法有:

1
2
3
4
5
6
7
8
let str = "Hello World!"
let first = str.startIndex // 字符串第一字符的 index值
str[first] # "H"

str.endIndex # 字符串最后一个字符
str.index(before: str.endIndex) // str.index(before:indexType) 获取indexType位置前一个字符的index
str.index(after: str.startIndex) // str.index(after:indexType) 获取indexType位置后一个字符的index
str.index(str.startIndex,offserBy:2) // str.index(indexType,offserBy:intType) 获取 indexType偏移量intType字符index

注意:str.endIndex获取是最后一个字符位置加一的值。越界获取字符会引发运行时错误。

string包含一个indices属性可以获取该字符串全部索引的范围。

1
2
3
4
5
let animal = "Mouse"
for index in animal.indices {
print("\(animal[index]) ", terminator: "")
}
// 打印输出 "M o u s e"

字符串中一个字符的插入和删除通过:insert(_,at:)remove(at:)完成。

1
2
3
4
5
6
var welcome = "hello"
welcome.insert("!", at: welcome.endIndex) // 插入一个字符
// welcome now equals "hello!"

welcome.insert(contentsOf: " there", at: welcome.index(before: welcome.endIndex)) // 插入一个字符串
// welcome now equals "hello there!"

插入和删除一个字符串通过:insert(contentsOf:at:)removeSubrange(_:)完成。

1
2
3
4
5
6
welcome.remove(at: welcome.index(before: welcome.endIndex)) // 删除一个字符
// welcome now equals "hello there"

let range = welcome.index(welcome.endIndex, offsetBy: -6)..<welcome.endIndex
welcome.removeSubrange(range) // 删除一个字符串
// welcome now equals "hello"

子字符串

从一个字符串获取一个子字符串通过:使用string.index(of:str)获取起始和结束字符index,然后利用区间获取子字符串。

1
2
3
4
5
6
7
let greeting = "Hello, world!"
let index = greeting.index(of: ",") ?? greeting.endIndex // 获取 ','index值,如果没有则获取greeting字符串长度index值
let beginning = greeting[..<index] // 利用区间获取子字符串
// beginning 的值为 "Hello"

// 把结果转化为 String 以便长期存储。 子字符串只能临时使用,如果要长期使用需转为string
let newString = String(beginning)

在Swift中子字符串指向原字符串某段字符串的内存地址。这是Swift性能优化的地方,这样避免了在获取子字符串时需要进行内存的拷贝和节省了内存空间。如果你需要长时间使用子字符串,你应该将子字符串转化成独立的字符串,因为如果子字符串一直在使用原来的字符串会一直存在,这样会有不必要的内存空间浪费。

元组

元组(tuples)把多个值组合成一个复合值。元组内的值可以是任意类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 创建一个元组
let tp1 = (28,180) //(int,int) 类型相同的元组
// 默认元组可以通过以 0 开头的下标读取元素值
print(tp1.0) // 28
print(tp1.1) // 180

let tp2 = (28,"Fynn") // (int,string)类型不同的元组
let (age,name) = tp2
// 通过内容分解,将元组分解为对应的变量或常量
print(age) // 28
print(name) // Fynn

let tp3 = (age:28,name:"Fynn") // 定义了元素名的 元组
// 通过元组的元素名 获取对应的值
print(tp3.age)
print(tp3.name)

使用场景:

  1. 当方法返回多个值时,可以使用元组类型。
  2. 两个值交换(a,b) = (b,a)
  3. 控制流中解析赋值

集合类型

Swift提供了ArraysSetsDictionaries三种基本的集合类型。

  1. Arrays保存有序数据的集合
  2. Sets保存无序无重复数据的集合
  3. Dictionaries保存无序的键值对的值

Swift中的集合类型存储的数据值类型必须明确,不能将不同类型值存放一起。

1
2
3
4
5
6
7
8
var arr = [Int]() // 声明一个空数值
arr.append(1) // 向空数值插入一个值

var name = Set<String>() // 创建一个空集合
name.insert("Fynn") // 插入一个名字

var nameAndAge = [String: Int]() // 创建一个空字典
nameAndAge["Fynn"] = 28

数组Arrays

通过Array(repeating:,count:)可以创建指定大小的默认值相同的数组。

1
var tenAge = Array(repeating:0, count: 10)

通过+符号可以将两个相同类型的数组相加。

1
2
3
4
5
6
7
8
9
10
11
var anotherAge = Array(repeating: 10,count:5)

var newAge = tenAge+another
newAge += [2,3] // 使用 += 直接在数组后面添加一个或多个相同类型的数据项
newAge.append(5) // 利用 append 添加相同数据项

newAge.insert(5,at:4) // 利用 insert(_:,at:)向指定索引值之前添加数据项

newAge.remove(at:5) // 移除指定位置的 数据项

newAge[2...6] = [2,3,4,5] // 通过区间 修改数组值

可以通过Array.count读取数组的长度,Array.isEmpty检测数组是否为空。

for-in循环中通过调用enumerated()方法返回一个元组,这个元组有获得数据项和其对应的索引值组成。

1
2
3
4
var name = ["Fynn","Echo","Mouse"]
for (index, value) in name.enumerated() {
print("index: \(index),name: \(value)")
}

集合(Sets)

集合存储着类型相同没有重复着的无序值。这些值类型一定提供了一种可以计算出其哈希值的方法。Swift中的基本类型(String,Int,Double,Bool)默认都有哈希值。字典的键类型也必须是可以计算出哈希值的。

你可以自己定义类型作为集合的值或字典的键。但种类型必须符合 Swift 标准库中的Hashable协议。符合Hashable协议的类型需要提供一个类型为Int的可读属性hashValue。

Hashable协议符合Equatable协议,Equateble协议要求实现“==”运算符方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 自定义一个类型
struct Persion {
var name: String
var age: Int
}
// 扩展实现 Equatable 协议
extension Persion: Equatable {
static func == (lhs: Persion, rhs: Persion) -> Bool {
return (lhs.name == rhs.name && lhs.age == rhs.age)
}
}
// 扩展实现 Hashable 协议
extension Persion: Hashable {
var hashValue: Int {
return name.hashValue^age.hashValue
}
}
// 定义个 Persion类型变量
var me = Persion(name:"Fynn",age: 28)

var family:Set = [me]

集合常用方法:

1
2
3
4
5
6
7
8
var name = Set<String>() // 定义一个空集合
var email:Set = ("fynn.90@outlook.com") // 定义一个 带有值的集合

name.insert["Echo"] // 向集合插入一个值
name.count // 获取一个集合大小
name.isEmpty // 判断是否为空
name.remove("Echo") // 移除 指定值
name.contains("Echo")

集合是无序的,但调用sorted()可以获取一个使用顺序操作符<排序后的可迭代对象。

1
2
3
4
5
6
7
let name: Set = ["Fynn","Echo","Mouse"]
for n in name.sorted() {
print("name: \(n)")
}
// Echo
// Fynn
// Mouse

集合之间操作:

  • 使用intersection(_:)方法根据两个集合中都包含的值创建的一个新的集合。
  • 使用symmetricDifference(_:)方法根据在一个集合中但不在两个集合中的值创建一个新的集合。
  • 使用union(_:)方法根据两个集合的值创建一个新的集合。
  • 使用subtracting(_:)方法根据不在该集合中的值创建一个新的集合。

集合之间关系:

  • 使用“是否相等”运算符(==)来判断两个集合是否包含全部相同的值。
  • 使用isSubset(of:)方法来判断一个集合中的值是否也被包含在另外一个集合中。
  • 使用isSuperset(of:)方法来判断一个集合中包含另一个集合中所有的值。
  • 使用isStrictSubset(of:)或者isStrictSuperset(of:)方法来判断一个集合是否是另外一个集合的子集合或者父集合并且两个集合并不相等。
  • 使用isDisjoint(with:)方法来判断两个集合是否不含有相同的值(是否没有交集)。

字典

字典用于存储相同类型的值的容器。每个值都关联唯一的键,键类型必须是遵循Hashable协议。

1
2
3
4
5
6
7
8
9
10
var namesAndAge = [String:Int]()
nameAndAge = ["Fynn":28,"Echo":28] // 字典也是用 []包裹着,和其他语言一般是用{}包裹键值对

// 通过 for-in遍历字典
for (key,value) in namesAndAge {
print("\(key):\(value)")
}

var names = namesAndAge.values // 获取 以字典值组成的数组
var ages = nameAndAge.keys // 获取 以字典键组成的数组

枚举

枚举(enum)为一组相关的值定义一个共同的类型,在代码中可以用类型安全的方式使用这些值。

1
2
3
4
5
6
enum Season { // enum 关键字 声明一个枚举类型
case spring // case 关键字 定义个枚举成员
case summer
case autumn
case winter
}

枚举在Swift中是一等类型(Class,struct都是,首字母推荐大写)。它支持一些类特性,例如:计算属性、枚举附加值、实例方法。

关联值

可以为枚举成员定义关联值。在声明枚举时,定义成员可以初始时可以传递的值类型。每个枚举成员定义的关联值类型不可以不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
var productBarcode = Barcode.upc(8,85909,51226,3)
productBarcode = .qrCode("ABCDEFGHIJKLMNOP") // 如果变量类型确定,再次赋值时可以省略类型名

switch productBarcode {
// 结构时let 可以省略
case .upc(let numberSystem, let manufacturer, let product, let check):
print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case .qrCode(let productCode):
print("QR code: \(productCode).")
}
// 打印 "QR code: ABCDEFGHIJKLMNOP."

原始值

在声明枚举时我们可以为成员设定默认值(原始值),这些原始值类型必须相同。

1
2
3
4
5
enum ASCIIControlCharacter: Character {
case tab = "\t"
case lineFeed = "\n"
case carriageReturn = "\r"
}

在声明枚举时需指定原始值类型,并且成员的原始值必须是唯一的。原始值设定后就不能改变。

如果设定原始值类型是整型或字符串类型,如果不设定原始值,则成员会获得默认原始值。

1
2
3
4
5
6
7
8
9
10
11
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
enum CompassPoint: String {
case north, south, east, west
}

print(Planet.venus.rawValue) // 2 通过 .rawValue读取原始值
print(CompassPoint.north.rawValue) // north

let possiblePlanet = Planet(rawValue: 7) // uranus 根据rawValue 获取枚举值

递归枚举

递归枚举是一种枚举类型,它有一个或多个枚举成员使用该枚举类型的实例作为关联值。需要在此成员变量前添加关键字indirect

1
2
3
4
5
enum ArithmeticExpression {
case number(Int)
indirect case addition(ArithmeticExpression, ArithmeticExpression)
indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}

运算符

运算符是一个编程语言的基础,Swift提供和其他语言差不多的运算符。有所区别的是Swift没有其他语言常见的自加减(++a,a++,—a,a—)!提供了在Swift常见的区间运算符、空合运算符。

区间运算符

闭区间运算符a…b 定义 一个ab所有值的区间(包含b)。a的值不能大于b。

1
2
3
for index in 1...5 {
print("\(index) * 5 = \(index * 5)")
}

半开区间运算符a..<b定义一个a至b不包含b的所有值。半开区间包含起始值但不包含结束值。在迭代数组时数组长度为结束值则就应该使用半开区间运算符。

1
2
3
4
5
let name = ["Fynn","Echo","Mouse"]
let count = name.count
for i in 0..<count {
print("index:\(i+1)--name:\(name[i])")
}

单侧区间运算符[a...][...a]一边到无穷大。半开区间也可以是单侧区间[...<a]

1
2
3
for i in [0...] {
print("index:\(i+1) -- name:\(name[i])")
}

空合运算符

Swift中有个表示空(无)的类型nil。当一个变量的值可能是nil时它就被称为可选型。空合运算符就是为可选型变量赋值给别人时设计的。

1
2
3
4
let defaultColorName = "red"
var userDefinedColorName: String? //默认值为 nil

var colorNameToUse = userDefinedColorName ?? defaultColorName // userDefinedColorName 值为nil时,将defaultColorName 值赋予colorNameToUse

控制流

控制流分为三类:

  1. 循环遍历:for-inwhilerepeat-while
  2. 条件语句:ifswitchwhereguard
  3. 控制转移:continuebreakreturnfallthroughthrow

switch

Swift中的switch语句每个判断条件后面可以不写break默认会自动跳出。并且编译器会要求将各种情况都列出来否则就会报错,所以default会作为最后的条件判断。

1
2
3
4
5
6
7
8
9
let name = "mouse"
switch name {
case "fynn":
print("you are fynn") // 每个 case语句后面都要求跟一个语句
case "echo":
print("you are echo")
case default:
print("you are mouse")
}

case语句后面的判断条件写法可以很丰富,可以是区间、逗号分隔多个值、元组(元组值可以是区间)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let num = 6
switch num {
case 0,1:// 逗号分隔多个值
print(0)
case 2...5:// 开区间
print("2...5")
case 6..<10:// 闭区间
print("6...10")
default:
print("10...")
}

let person = (28,"Fynn")
switch person {
case (let age,"Fynn"): // age 可以是 let常量,也可以是 var 变量
print("age:\(age),name:Fynn")
case (let age, "Echo") where age != 28: // 通过 where添加判断条件
print("age:\(age),name:Echo")
case let (age,name): // 赋值结构
print("age: \(age),name:\(name)")
}

Switch 默认匹配到满足条件后会自动跳出,如果需要继续向下匹配可以在case分支尾部使用fallthrough关键字。

guard

guard Condition else {}guard默认会跟着一个else,当Condition条件为真时继续执行,否则进入else分支。常用于方法内条件判断不符合就提前提出。

1
2
3
4
5
6
7
8
9
var person = ["name":"Fynn","age":"28"]
func greet(_ person:[String:Int]) {
guard person["name"] != nil else {
print("what's you name?")
return
}
print("hello \(person.name)")
}
greet(person)

函数和闭包

函数

Swift中的函数最大的特点就是参数形态很多,除了常见的有参,无参,默认参数。还可以设置外部参数名,内部参数名,输入参数,可变参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 有参函数
func greet (name: String) {
print("hello,\(name)")
}

// 无参函数
func greetWorld () {
print("hello,world!")
}

// 默认参数
func greetPerson (name: String = "world") {
print("hello,\(name)")
}

/*
*设置外部参数和内部参数,Swift提供外部参数和内部参数是为了语意上更清晰
*因为很多情况下调用函数时该参数意义和内部该参数意义是不一样的。
*/
func person (name parameter1:String,age parameter2: Int) {
print("name: \(parameter1),age: \(parameter2)")
}
person(name:"Mouse",age: 1)

// 输入输出参数 在参数类型前添加关键字 inout。在函数内修改参数的值在函数外变量的值也会真实被修改。
func prefixName (_ name: inout String) {
name = "sir,\(name)"
}
var name = "Fynn"
prefixName(&name)
print(name)

// 可变参数 将以数组形式传入函数内部 参数类型都应该一致,并可变参数只能是最后一个参数
func maxNum (_ numbers: Int...) -> Int {
var num = 0
for n in numbers {
if num < n {
num = n
}
}
return num
}
maxNum(3,6,10,8,6)

上面的例子都是函数没有返回值,如果函数有返回值需要声明返回值数量和值类型。如果是多个参数以元组形式返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 返回一个参数
func person (_ name: String,age: Int) -> String {
return "name:\(name),age:\(age)"
}

// 返回多个参数
func maxAndMin (_ numbers: Int...) ->(max:Int,min:Int) {
var currentMin = numbers[0]
var currentMax = numbers[0]
for value in numbers[1..<numbers.count] {
if value < currentMin {
currentMin = value
} else if value > currentMax {
currentMax = value
}
}
return (max:currentMax, min:currentMin)
}
let (maxNumber,minNumber) = maxAndMin(4,-1,5,8,9,100,-3)

闭包

闭包是可以没有函数名、参数名自包含代码块。它可以捕获和存储其所在上下文中任意变量和常量的引用。

1
2
3
4
// 闭包完整表达式
{(parameters) -> returnType in
statements
}

闭包的参数可以是 in-out参数、可变参数,但不能设置默认值。元组可以作为参数或返回值。

闭包多种简写形式,以Array.sorted(by:)为例,它用于对数组元素进行排序。Array.sorted(by:)接受一个闭包作为参数,闭包默认接受两个值作为参数,如果第一值应该出现在第二个值前面闭包函数应该返回true,反之返回false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let names = ["Fynn","Echo","Mouse"]
func backward (_ s1:String, _ s2: String) {
return s1 > s2
}

// 最常见的方式 传递一个函数
names.sorted(by: backward)

// 典型写法
names.sorted(by:{(s1: String, s2:String) -> Bool in
return s1>s2
})

// 代码较短时可以写为一行
names.sorted(by:{(s1:String,s2:String)->Bool in return s1>s2})

// 因为排序闭包函数传入的参数,Swift可以推断其参数和返回值的类型,所以参数类型和返回值类型也可以省略。
names.sorted({by:{s1,s2 in return s1 > s2}})

// 单行表达式闭包可以省略 return 关键字,自动隐式返回单行表达式的结果。
names.sorted(by:{s1, s2 in s1 > s2}})

// Swift闭包提供参数缩写,你可以通过 $0,$1,$2来顺序调用闭包参数
names.sorted(by:{$0 > $1})

尾随闭包

当闭包作为最后一个参数传递给函数时,可以使用尾随闭包写法简化代码的编写。

1
2
3
4
5
6
7
8
9
10
func someFn (_ name:String,fn: (String)->Void ) {
fn(name)
}

someFn("Fynn",fn:{
print("hello \($0)")
})
someFn("Echo"){
print("hello \($0)")
}

逃逸闭包

当一个闭包作为参数传递给函数,但要在函数返回后才被执行,则这个闭包被称为逃逸闭包。如果是逃逸闭包需要用@escaping关键字定义它。并且逃逸闭包如果要使用其上下文需要显式的引用self

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}

func someFunctionWithNonescapingClosure(closure: () -> Void) {
closure()
}

class SomeClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure { self.x = 100 }
someFunctionWithNonescapingClosure { x = 200 }
}
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)
// 打印出 "200"

completionHandlers.first?()
print(instance.x)
// 打印出 "100"

类和结构体

Swift中的类(class)和结构体(struct)及其相似。它们共同点有:

  • 定义属性
  • 定义方法
  • 自定义下标
  • 定义构造函数
  • 定义扩展
  • 实现协议

类和结构体看起来一摸一样,但两个关键特性使得它们应用场景区别开来

  • 类是引用类型,结构体是值类型
  • 类可以继承,结构体没有继承

结构体默认拥有逐一构造器,而类没有。

1
2
3
4
5
struct point {
var x = 0
var y = 0
}
let p = point(x:120,y:120) // (x:y:)逐一构造器

Swift所有基础类型(Int、Float、Bool、String、Array、Dictionary)在底层都是通过结构体实现。

值类型,在赋值时是通过拷贝变量存储的真实值。而引用类型,在赋值时是传递真实数据内存的引用。

判断两个变量或常量是否引用同一个类实例通过恒等运算符:===!==

类和结构体使用区别:

  1. 结构体表示具体的值,类表示抽象性的物体
  2. 想拥有继承特性就该使用类
  3. 更高的效率该应用结构体

属性&方法&下标

属性

计算属性可以在类(Class)、结构体(Struct)、枚举(Enum)中关联一个值。而存储属性只能用于类(Class)、结构体(Struct)。

存储属性和计算属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
struct Point {
var x = 0.0, y = 0.0
}
struct Size {
var width = 0.0, height = 0.0
}
struct Rect {
var origin = Point() // 存储属性
var size = Size() // 存储属性
var center: Point { // 计算属性 计算属性代码块内有 getter和setter用于读取、设置属性
get { // 只读属性 get关键字可以省略
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set(newCenter) { // 如果没setter 则该属性是只读属性
origin.x = newCenter.x - (size.width / 2)
origin.y = newCenter.y - (size.height / 2)
}
}
}
var square = Rect(origin: Point(x: 0.0, y: 0.0),
size: Size(width: 10.0, height: 10.0))
let initialSquareCenter = square.center
square.center = Point(x: 15.0, y: 15.0)
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
// 打印 "square.origin is now at (10.0, 10.0)”

注意:结构体的实例赋予一个常量则它的属性将不能修改,因为它是值类型。

延迟存储属性

当初始化属性值需要大量复杂工作,而这些工作并不是构造时需要的时候,为了提高效率我们可以将这个属性定义为延迟存储属性lazy。当第一次调用这个属性再去初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class DataImporter {
/* DataImporter 是一个负责将外部文件中的数据导入的类。 这个类的初始化会消耗不少时间。 */
var fileName = "data.txt"
// 这里会提供数据导入功能
}

class DataManager {
lazy var importer = DataImporter()
var data = [String]()
// 这里会提供数据管理功能
}

let manager = DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")
// DataImporter 实例的 importer 属性还没有被创

属性观察器

属性观察器willSet在属性值在被设新值前会被调用,willSet观察器会将新值作为参数(默认参数名:newVale)传入。你可以 自定义此参数名。

属性观察器didSet在属性被设置后被调用,旧值会作为参数(默认参数:oldValue)传入。如果你在此观察器内对其赋值,新值会被旧值覆盖。

willSet无法修改属性值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Num {
var n = 0 {
willSet {
print(newValue)
// n = 9 无效赋值 Xcode 会给警告
}

didSet {
print(oldValue)
n = 1
}
}
}
var n = Num()
n.n = 3
n.n // 永远是1

静态属性

通过关键字static可以定义一个静态属性(类型属性),它属于包含的数据类型而不属于任何实例,可以通过类型名直接获取赋值,实例间可以共享它。

方法

实例方法

实例方法是属于某个特定类、结构体或者枚举类型实例的方法。实例方法提供访问和修改实例属性的方法或提供与实例目的相关的功能,并以此来支撑实例的功能。

值类型(结构体,枚举)的属性不能在实例中被修改。如果你想在方法中修改值类型的属性值,你需要在该方法前添加关键字mutating

1
2
3
4
5
6
7
8
9
struct point {
var x = 0.0, y = 0.0
mutation func move (deltaX: Double, deltaY: Double) {
x += deltaX
y += deltaY
}
}
var somePoint = point(0.0,0.0)
somePoint.move(deltaX: 40.0, deltaY: 20.9)

静态方法

静态方法输入包含它的类型而不是某个实例,可以通过类型名称直接调用它。在func之前添加关键字static

下标

Javascript中数组、object可以通过下标访问内部值。而在Swift中你可以给类(Class)、结构体(struct)、枚举(enum)定义下标,用于获取内部值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
struct Matrix {
let rows: Int, columns: Int
var grid: [Double]
init(rows: Int, columns: Int) {
self.rows = rows
self.columns = columns
grid = Array(repeating: 0.0, count: rows * columns)
}
func indexIsValid(row: Int, column: Int) -> Bool {
return row >= 0 && row < rows && column >= 0 && column < columns
}
// 定义 下标方法
subscript(row: Int, column: Int) -> Double {
// 获取值
get {
assert(indexIsValid(row: row, column: column), "Index out of range")
return grid[(row * columns) + column]
}
// 设置值
set {
assert(indexIsValid(row: row, column: column), "Index out of range")
grid[(row * columns) + column] = newValue
}
}
}
var matrix = Matrix(rows: 2, columns: 2)
matrix[0, 1] = 1.5
matrix[1, 0] = 3.2

subscript可以任意类型和数量的值作为参数,并返回任意类型的值。

继承

Swift一个类只能继承一个父类,而子类重写父类的属性和方法需要使用关键字override声明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Car {
var currentSpeed = 0.0;
var description: String { // 只读计算属性
return "traveling at \(currentSpeed) miles per hour";
}

func makeNoise () {

}
}
class AutomaticCar: Car {
var gear = 1;
// 重复 父类属性currentSpeed,添加属性观察器
override var currentSpeed: Double {
didSet {
gear = Int(currentSpeed / 10.0) + 1
}
}
// 重写 父类makeNoise方法
override func makeNoise() {
print("Choo Choo")
}
// 重写 父类description只读计算属性
override var description: String {
return super.description + " in gear \(gear)"
}
}

注意: 你不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器,因为它们不会被设置值。

如果父类某些特性不想被子类重写,可以通过关键字final 来防治它们被重写。例如:final varfinal funcfinal class funcfinal subscript

构造

在使用类(class)、结构体(struct)、枚举(enum)实例之前的准备过程被称为构造。

Swift规定类、结构体中的存储属性在实例创建完成前必须要有初始值。有两种方式设定该初始值,一个是在定义该存储属性时给它设定默认值,另外一种就是在构造时设定初始值。

构造语法规则是,在类、结构体、枚举内部定义名字为init的方法。

1
2
3
init () {
// 构造内容
}

如果类或结构体的存储属性都设置了默认值,同时没有自定义构造器,那么Swift会为它们提供一个默认构造器,(无参数实例话该类)。

1
2
3
4
5
6
class ShoppingListItem {
var name: String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()

Swift还为没有自定义构造器提供了成员逐一构造器。逐一构造器中的参数名就是该结构体中声明的属性名。即使这些属性没有设置默认值,逐一成员构造器依然有效。

1
2
3
4
struct Size {
var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)

假如你自定义了构造器,则默认构造器和逐一成员构造器将不存在,你想要在自定义的构造器确保每个存储属性都设置的初始值。

自定义构造器

Swift可以为类、结构体、枚举定义多个自定义构造器。通过构造参数名(外部参数名,内部参数名)、参数类型来区分这些构造器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct Celsius {
var temperatureInCelsius: Double
init(fromFahrenheit fahrenheit: Double) {
temperatureInCelsius = (fahrenheit - 32.0) / 1.8
}
init(fromKelvin kelvin: Double) {
temperatureInCelsius = kelvin - 273.15
}
// 不设置外部参数名
init(_ celsius: Double){
temperatureInCelsius = celsius
}
}
// 通过参数名区分调用的构造器
let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)
// boilingPointOfWater.temperatureInCelsius 是 100.0
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius 是 0.0

// 不传入参数名
let bodyTemperature = Celsius(37.0)
// bodyTemperature.temperatureInCelsius 为 37.0

上面例子中:init(fromFahrenheit)init(fromKelvin)构造器参数类型相同,通过外部参数名做区分。你也可以不设定外部参数,在调用构造器时直接传入形参,例如init(_ celsius)

注意: 常量存储属性可以在构造器中指定其值。类类型中,常量属性只能在定义它的类构造器中修改值,子类不能修改。

值类型的构造代理

自定义构造器可以定义多个,你可以在一个自定构造器中通过self.init调用另一个自定义构造器完成所有存储属性的初始化。这些实现代码复用,简化代码。这样的构造方式称为构造代理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Rect {
var origin = Point()
var size = Size()
init() {}
init(origin: Point, size: Size) {
self.origin = origin
self.size = size
}
init(center: Point, size: Size) {
let originX = center.x - (size.width / 2)
let originY = center.y - (size.height / 2)
self.init(origin: Point(x: originX, y: originY), size: size)
}
}

class的构造器类型

Swift中的类有两种构造器,一种是在构造器中初始化了所有存储属性值,并通过调用父类构造器完成了父类实例话(父类的属性必须在父类中的构造器完成初始化),这样的构造器称为指定构造器。每个类必须至少要有一个指定构造器。另外一种构造器是便利构造器,它必须能够间接或直接的调用同类中的一个指定构造器,在便利构造器中整理存储属性值,并通过调用其他构造器传递出去,最后在指定构造器中实现所有存储属性的初始化。必须通过convenience关键字定义便利构造器。

类的构造器代理规则:

  1. 指定构造器必须调用其直接父类的的指定构造器。
  2. 便利构造器必须调用类中定义的其它构造器。
  3. 便利构造器必须最终导致一个指定构造器被调用。

便利构造器:

1
2
3
4
5
6
7
8
9
10
class Food {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[Unnamed]")
}
}
let empty = new Food(); // 调用便利构造器

上面例子构造链:

子类构造器:

1
2
3
4
5
6
7
8
9
10
11
class RecipeIngredient: Food {
var quantity: Int
init(name: String, quantity: Int) {
self.quantity = quantity // 自有属性初始化 在完成自有属性初始化后,才能调用父类构造器
super.init(name: name) // 调用父类构造器方法,实现实例话
}
// 便利构造器 默认指定quantity值为1
override convenience init(name: String) {
self.init(name: name, quantity: 1)
}
}

上面例子构造链:

类的两段式构造

类构造分为两个部分:

  1. 初始化各个属性值。在这个部分中self是不存在的,你不能通过self获取属性值或调用方法。
  2. 优化已经初始化的属性值。这个部分self存在了,你可以调用它获取属性值或方法修改完成初始化的属性值。

判断第一阶段结束的一个标准是子类调用了父类的构造器。而子类的自有的属性必须在调用父类构造器前完成初始化。

图解两段式构造过程第一阶段:

构造过程从子类的便利构造器开始,这个便利构造器此时没法修改任何属性,它把构造任务代理给同一类中的指定构造器。指定构造器在保证自有属性都完成初始化后调用父类指定构造器。父类指定构造器在完成所有属性初始化后就完成了构造第一阶段。

两段式构造过程第二阶段:

父类指定构造器在完成所有属性初始化后,可以进一步定制实例。父类指定构造器调用完成,子类指定构造器就可以定制实例,最后当指定构造器调用完成后,便利构造器可以进行定制实例。

父类的构造器重写和继承

当你在子类编写一个和父类构造器模式相匹配的构造器时,你其实在重写父类构造器,这时你需要在重写的构造器前添加关键字override。即使你重写的是系统自带的构造器(默认构造器)都需要带关键字override

当你将父类指定构造器重写为便利构造器需要添加关键字overried,但你重写父类的便利构造器时则无需添加关键字override

一般情况子类是不会自动继承父类构造器,但在满足特定条件下子类构造器会继承父类构造器。

如果子类构造器自己的属性都提供了初始值,一下两种方式它将可以继承父类构造器。

  1. 如果子类没有定义任何指定构造器,它将自动继承所有父类的指定构造器。
  2. 如果子类提供了所有父类指定构造器的实现——无论是通过规则 1 继承过来的,还是提供了自定义实现——它将自动继承所有父类的便利构造器。

如果子类添加了多个便利构造器,这两条依然受用。

在构造前添加关键字require则表示这个构造器是必要构造器,所有子类必须实现该构造器。子类在重写父类必要构造器时必须在子类构造器面前添加关键字require,而不需要添加关键字override

可失败构造器

在类、结构体、枚举构造过程中可能构造失败(无效参数,缺少所需外部资源,不满足某种必要条件),这时你需要定义一个可失败构造器应对这种情况。

可失败构造器语法是在init关键字后面加一个问号(init?)。可失败构造器的参数名和参数类型,不能与其它非可失败构造器的参数名,及其参数类型相同。可失败构造器可以调用return nil来表明可失败构造器在何种情况下应该“失败”。

析构器

析构器可以在一个类的实例被释放前自动被Swift调用,如果你在该类中有定义它。

析构器的定义方式是通过关键字deinit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class Bank {
//类变量
static var coinsInBank = 10_000
// 类方法
static func distribute(coins numberOfCoinsRequested: Int) -> Int {
let numberOfCoinsToVend = min(numberOfCoinsRequested, coinsInBank)
coinsInBank -= numberOfCoinsToVend
return numberOfCoinsToVend
}
static func receive(coins: Int) {
coinsInBank += coins
}
}

class Player {
var coinsInPurse: Int
// 构造器
init(coins: Int) {
coinsInPurse = Bank.distribute(coins: coins)
}
func win(coins: Int) {
coinsInPurse += Bank.distribute(coins: coins)
}
// 析构器
deinit {
Bank.receive(coins: coinsInPurse)
}
}

// 声明可选变量
var playerOne: Player? = Player(coins: 100)
print("A new player has joined the game with \(playerOne!.coinsInPurse) coins")
// 打印 "A new player has joined the game with 100 coins"
print("There are now \(Bank.coinsInBank) coins left in the bank")
// 打印 "There are now 9900 coins left in the bank"

// “!”号是强制转换的意思,当变量是可选类型时Swift会先检查是否不是nil,如果你确定不是nil就可以使用强制符合剩去检测。
playerOne!.win(coins: 2_000)
print("PlayerOne won 2000 coins & now has \(playerOne!.coinsInPurse) coins")
// 输出 "PlayerOne won 2000 coins & now has 2100 coins"
print("The bank now only has \(Bank.coinsInBank) coins left")
// 输出 "The bank now only has 7900 coins left"

// 给变量设置nil,断开实例的一个计算引用
playerOne = nil
print("PlayerOne has left the game")
// 打印 "PlayerOne has left the game"
print("The bank now has \(Bank.coinsInBank) coins")
// 打印 "The bank now has 10000 coins"

Swift通过引用计数(ARC)处理实例的内存管理,当一个实例引用计数为0时代表它所占用的内存将可以被释放。在释放前析构器将被调用。

扩展

扩展是给已经定义(无论是自己还是Swift的)好的类(class)、结构体(struct)、枚举、协议(protocol)添加额外的功能。常见的用法是给Swift定义好的类型添加自己需要的方法或计算属性,以达到方便使用。

扩展支持的额外功能有:

  • 添加计算型属性和计算型类型属性,存储型属性和观察器属性不能添加。
  • 定义实例方法和类型方法。
  • 提供型的构造器。如果你需要自定义构造器而又不想覆盖系统自带的默认构造器和逐一构造器,你可以将自定义构造器通过扩展添加。
  • 定义下标
  • 定义和使用新的嵌套类型
  • 使一个已有类型符合某个协议
  • 扩展协议,提供协议要求的实现,或添加额外的功能,从而让符合这个协议的类型拥有这些功能

扩展的语法是:

1
2
3
4
5
6
extension someTypeprotocolTyle {
// 计算属性
// 方法
// 下标
// 构造器
}

计算属性

扩展Double 给它添加计算属性使它快速将不同的距离单位转换成“米”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
extension Double {
var km: Double { return self * 1_000.0 } // 千米
var m : Double { return self } // 米
var cm: Double { return self / 100.0 } // 厘米
var mm: Double { return self / 1_000.0 } // 毫米
var ft: Double { return self / 3.28084 } // 英尺
}
let oneInch = 25.4.mm
print("One inch is \(oneInch) meters")
// 打印 “One inch is 0.0254 meters”
let threeFeet = 3.ft
print("Three feet is \(threeFeet) meters")
// 打印 “Three feet is 0.914399970739201 meters”
let aMarathon = 42.km + 195.m
print("A marathon is \(aMarathon) meters long")
// 打印 “A marathon is 42195.0 meters long”

构造器

扩展可以为以后类型添加构造器,但只能为类添加便利构造器不能添加指定构造器和解析器。

如果扩展给值类型添加构造器,这些值类型没有自定义构造器并且存储属性有默认值,则在扩展添加的构造器内可以调用默认构造器或逐一构造器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
}
extension Rect {
init(center: Point, size: Size) {
let originX = center.x - (size.width / 2)
let originY = center.y - (size.height / 2)
// 调用 自带的逐一构造器
self.init(origin: Point(x: originX, y: originY), size: size)
}
}

方法

扩展Int让它拥有so cool的能力。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
extension Int {
func repetitions(task: () -> Void) {
for _ in 0..<self {
task()
}
}
// 结构体,枚举修改变量需要添加 mutating关键字
mutating func square() {
self = self * self
}
}
3.repetitions({
print("Hello!")
})
5.repetitions {
print("Goodbye!")
}
var someInt = 3
someInt.square()
// someInt 的值现在是 9

下标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
extension Int {
// 返回数字从右向左数的第 n 个数字
subscript(digitIndex: Int) -> Int {
var decimalBase = 1
for _ in 0..<digitIndex {
decimalBase *= 10
}
return (self / decimalBase) % 10
}
}
746381295[0]
// 返回 5
746381295[1]
// 返回 9
746381295[2]
// 返回 2
746381295[8]
// 返回 7

协议

Swift中的协议类似其他高级编程语言的接口。它用于定义一套标准,符合它要求的类型必须实现这些标准。类、结构体、枚举可以遵循协议。

属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protocol SomeProtocol {
// 定义协议
// 协议定义的属性都是使用var 声明变量属性
/*
* 大括号中的 {get set}指明该属性是可读可写的。如果是{get}说明该属性是只读的,
* 这时在实现该属性时可以定义为常量或只读的计算属性。
*/
var mustBeSettable:Int { get set}

// {get}类型的属性,在实现时可以为可读可写的。
var doesNotNeedToBeSettable: String { get }

// 可以用 static关键字用于属性的前缀。当时类类型时还可以用class关键字声明类型
static var someTypeProperty: Int { get set }
}

协议不指定属性是存储型属性还是计算型属性,它只指定属性的名称和类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
protocol FullyNamed {
var fullName: String { get }
}

struct Person: FullyNamed {
var fullName: String // 实现时可以是可读可写 虽然协议定义是只读
}
let john = Person(fullName: "John Appleseed")
// john.fullName 为 "John Appleseed"

class Starship: FullyNamed {
var prefix: String?
var name: String
init(name: String, prefix: String? = nil) {
self.name = name
self.prefix = prefix
}
// 只读的计算属性
var fullName: String {
return (prefix != nil ? prefix! + " " : "") + name
}
}
var ncc1701 = Starship(name: "Enterprise", prefix: "USS")
// ncc1701.fullName 是 "USS Enterprise"

方法

1
2
3
4
5
protocol SomeProtocol {
// 协议中的方法需要定义方法名和返回值类型,如果没有返回值可以不写。
// 协议中的方法可以是 static或class
static func someTypeMothod () -> Double
}

协议的方法不需要大括号和方法体。可以在协议中定义具有可变参数的方法,但不能指定参数默认值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
protocol RandomNumberGenerator {
// 定义一个 random方法 返回值是 Double
func random() -> Double
}

class LinearCongruentialGenerator: RandomNumberGenerator {
var lastRandom = 42.0
let m = 139968.0
let a = 3877.0
let c = 29573.0
func random() -> Double {
lastRandom = ((lastRandom * a + c).truncatingRemainder(dividingBy:m))
return lastRandom / m
}
}
let generator = LinearCongruentialGenerator()
print("Here's a random number: \(generator.random())")
// 打印 “Here's a random number: 0.37464991998171”
print("And another one: \(generator.random())")
// 打印 “And another one: 0.729023776863283”

protocol Togglable {
// 如果方法内会修改属性值,则应在func 前添加 mutating关键字
// 类类型方法不需要写 mutating关键字,结构体和枚举类型需要写 mutating关键字
mutating func toggle()
}

enum OnOffSwitch: Togglable {
case off, on
mutating func toggle() {
switch self {
case .off:
self = .on
case .on:
self = .off
}
}
}
var lightSwitch = OnOffSwitch.off
lightSwitch.toggle()

构造器

1
2
3
4
5
6
7
8
9
10
11
12
protocol SomeProtocol {
// 遵循该协议的类型必须实现该构造器
init(someParameter: Int)
}

class SomeClass: SomeProtocol {
// 实现协议中的构造器 需要在构造器前添加关键字 required,表示子类需实现该构造器
// 如果该类 用 final定义了则不需要 添加 required
required init(someParameter: Int) {
// 这里是构造器的实现部分
}
}

如果一个子类重写了父类的构造器并且又实现了协议中该构造器,那么在实现该构造器时需要添加 requiredoverride关键字。

作为类型

协议可以作为一种类型,用于定义变量、常量、或属性的类型。也可以用于声明方法,函数,构造器参数类型或返回值类型。

当作为方法参数类型时,如果某个参数遵循多个协议,这些协议用”&”符号相连接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
struct Person: Named, Aged {
var name: String
var age: Int
}
func wishHappyBirthday(to celebrator: Named & Aged) {
print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
let birthdayPerson = Person(name: "Malcolm", age: 21)
wishHappyBirthday(to: birthdayPerson)
// 打印 “Happy birthday Malcolm - you're 21!”

协议也可以用于定义数组和字典集合类型。let things: [TextRepresentable] = [game, d12, simonTheHamster]

检查协议一致性

协议既然是一种普通类型,那么就可以通过isas检测某个实例的类型是否遵循某个协议。

  • is 用来检查实例是否符合某个协议,若符合则返回 true,否则返回 false
  • as? 返回一个可选值,当实例符合某个协议时,返回类型为协议类型的可选值,否则返回 nil
  • as! 将实例强制向下转换到某个协议类型,如果强转失败,会引发运行时错误。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 定义一个协议
protocol HasArea {
// 拥有这个属性
var area: Double { get }
}

/*
* Circle和Country 遵循HasArea协议。
* Circle area是个只读计算属性
* Country area是个存储属性
*/
class Circle: HasArea {
let pi = 3.1415927
var radius: Double
var area: Double { return pi * radius * radius }
init(radius: Double) { self.radius = radius }
}
class Country: HasArea {
var area: Double
init(area: Double) { self.area = area }
}

// Animal 没有遵循HasArea协议
class Animal {
var legs: Int
init(legs: Int) { self.legs = legs }
}

// 定义一个objects数组 集合类型是 AnyObject
let objects: [AnyObject] = [
Circle(radius: 2.0),
Country(area: 243_610),
Animal(legs: 4)
]

for object in objects {
// 用 as? 判断对象是否遵循 HasArea
if let objectWithArea = object as? HasArea {
print("Area is \(objectWithArea.area)")
} else {
print("Something that doesn't have an area")
}
}
// Area is 12.5663708
// Area is 243610.0
// Something that doesn't have an area

扩展协议

通过扩展使已有类型遵循某个协议。

1
2
3
4
5
6
7
8
9
10
11
12
13
protocol TextRepresentable {
var textualDescription: String { get }
}
// 通过扩展使得Dice遵循 TextRepresentable
extension Dice: TextRepresentable {
var textualDescription: String {
return "A \(sides)-sided dice"
}
}

let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())
print(d12.textualDescription)
// 打印 “A 12-sided dice”

通过扩展为一遵循某个协议的类型提供属性、方法和下标的实现。这样的方式可以为以遵循协议添加共有的方法。协议本来是不可以拥有具体的实现,但通过扩展可以让协议拥有具体的实现并让遵循它的类型自动拥有这些实现。

如果遵循协议的类型自己定义了这些实现,则自定义的实现会代替扩展中默认的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 为 RandomNumberGenerator 提供 randomBool默认实现
extension RandomNumberGenerator {
func randomBool() -> Bool {
return random() > 0.5
}
}

// LinearCongruentialGenerator 遵循 RandomNumberGenerator
//通过协议扩展,所有遵循协议的类型,都能自动获得这个扩展所增加的方法实现,无需任何额外修改
let generator = LinearCongruentialGenerator()
print("Here's a random number: \(generator.random())")
// 打印 “Here's a random number: 0.37464991998171”
print("And here's a random Boolean: \(generator.randomBool())")
// 打印 “And here's a random Boolean: true”

你还可以为协议扩展添加条件,只有遵循协议并满足限制条件的类型才能获得协议提供的默认实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
protocol TextRepresentable {
var textualDescription: String { get }
}

struct Hamster {
var name: String
var textualDescription: String {
return "A hamster named \(name)"
}
}
// Hamster遵循 TextRepresentable协议 通过定义一个空扩展使Hamster 遵循TextRepresentable
extension Hamster: TextRepresentable {}

/* 扩展集合 协议 Array遵循这个协议。
* 只有集合元素遵循 TextRepresentable 协议 才能使用默认实现 textualDescription 计算属性
*/
extension Collection where Iterator.Element: TextRepresentable {
var textualDescription: String {
let itemsAsText = self.map { $0.textualDescription }
return "[" + itemsAsText.joined(separator: ", ") + "]"
}
}

let murrayTheHamster = Hamster(name: "Murray")
let morganTheHamster = Hamster(name: "Morgan")
let mauriceTheHamster = Hamster(name: "Maurice")
// hamsters 是个 集合类型是 Hamster 的Array
let hamsters = [murrayTheHamster, morganTheHamster, mauriceTheHamster]

/*
* 因为 hamsters 内部元素都遵循TextRepresentable协议,
* 所以它可以调用Collection 默认实现 textualDescription
*/
print(hamsters.textualDescription)
// 打印 “[A hamster named Murray, A hamster named Morgan, A hamster named Maurice]”

如果多个协议扩展都为同一个协议要求提供了默认实现,而遵循协议的类型又同时满足这些协议扩展的限制条件,那么将会使用限制条件最多的那个协议扩展提供的默认实现。

协议的继承

协议可以像类一样继承一个或多个协议。

1
2
3
4
5
6
7
/* 
* PrettyTextRepresentable 协议继承了 TextRepresentable协议。
* 遵循PrettyTextRepresentable 协议的实例也要实现 TextRepresentable中的要求
*/
protocol PrettyTextRepresentable: TextRepresentable {
var prettyTextualDescription: String { get }
}

如果你希望协议只被类遵循,可以是协议继承class。这样该协议只能被类遵循。

1
2
3
4
// class 协议必须放在继承列表第一位
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
// 这里是类类型专属协议的定义部分
}

泛型

泛型是强类型语言为了保持代码通用性的一种语法。它可以让变量类型可以在使用它之前指定。

1
2
3
4
5
6
// 交换 a、b值
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}

上面例子是交换参数ab值,如果参数类型是不确定的,只有调用时才知道,这时我们就可以使用泛型了用来满足这个场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 参数类型在声明中个方法时不确定的,唯一确定的是这两个参数类型是相同的!
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}

var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt 现在 107, and anotherInt 现在 3

var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString 现在 "world", and anotherString 现在 "hello"

上面例子中T称为占位符类型,在调用swapTwoValues才会确定它的类型。
如果需要多个占位符,它们都可以写在尖括号里面,用逗号分隔。

泛型类型

泛型除了用于函数,也可以应用在类(Class)、结构体(Struct)、枚举(Enun)。

定义一个泛型结构体,用于处理数组的添加项和移除项。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 数组类型 只有在调用时才知道
struct Stack<Element> {
// 创建 items 属性,使用 Element 类型的空数组对其进行初始化
var items = [Element]()

//指定 push(_:) 方法的唯一参数 item 的类型必须是 Element 类型
mutating func push(_ item: Element) {
items.append(item)
}

//指定 pop() 方法的返回值类型必须是 Element 类型
mutating func pop() -> Element {
return items.removeLast()
}
}

// 在调用 Stack 指明该结构体处理的项目是String类型
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
stackOfStrings.push("cuatro")

let fromTheTop = stackOfStrings.pop()
// fromTheTop 的值为 "cuatro",现在栈中还有 3 个字符串

可以使用扩展给一个泛型类型添加新功能

1
2
3
4
5
6
extension Stack {
// 加了一个名为 topItem 的只读计算型属性,它将会返回当前栈顶端的元素而不会将其从栈中移除
var topItem: Element? {
return items.isEmpty ? nil : items[items.count - 1]
}
}

泛型约束

在定义泛型类型时,可以指定调用时的类型必须满足的条件,这被称为泛型约束。

泛型约束可以使得代码具有灵活性的情况下保证了代码的可靠性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 泛型约束 可以指定类型必须是某个类的实例或遵循某个协议
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// 这里是泛型函数的函数体部分
}

// findIndex查找 数组中存在指定的值。
/* 因为在函数内部 使用 == 符号比较两个类型的值,
* 而Swift中只有遵循Equatable协议的类型才能使用 == 符号,否则编译时会报错。
*/
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}

let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
if let foundIndex = findIndex(ofString: "llama", in: strings) {
print("The index of llama is \(foundIndex)")
}
// 打印 “The index of llama is 2”

// 只有类型遵循 Equatable协议,findIndex方法都可以被调用成功。
let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
// doubleIndex 类型为 Int?,其值为 nil,因为 9.3 不在数组中
let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
// stringIndex 类型为 Int?,其值为 2

关联类型

在协议中为某个类型提供占位符被称为关联类型。需要在协议中使用associatedtype声明占位符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/*
* 任何遵循 Container协议的类型必须指定其存储的元素类型,并保证只有正确的类型才能添加进容器
* 并且可以通过下标返回类型。
*/
protocol Container {
// 声明占位符 ItemType
associatedtype ItemType

// 必须可以通过 append(_:) 方法添加一个新元素到容器里
mutating func append(_ item: ItemType)

// 必须可以通过 count 属性获取容器中元素的数量,并返回一个 Int 值。
var count: Int { get }

// 必须可以通过索引值类型为 Int 的下标检索到容器中的每一个元素
subscript(i: Int) -> ItemType { get }
}

// 遵循 Container协议的Stack
// 因为Swift类型推断机制,你不需要通过 typealias 声明 ItemType 类型
struct Stack<Element>: Container {
// Stack<Element> 的原始实现部分
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
// Container 协议的实现部分
mutating func append(_ item: Element) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Element {
return items[i]
}
}

泛型where语句

同过where关键字添加条件以约束类型应用场景。通过泛型约束有时并不能满足类型的全部约束条件,通过where可以完善约束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// C1 必须符合 Container 协议(写作 C1: Container)
// C2 必须符合 Container 协议(写作 C2: Container)
// C1 的 ItemType 必须和 C2 的 ItemType类型相同(写作 C1.ItemType == C2.ItemType
// C1 的 ItemType 必须符合 Equatable 协议(写作 C1.ItemType: Equatable)
func allItemsMatch<C1: Container, C2: Container> (_ someContainer: C1, _ anotherContainer: C2) -> Bool
where C1.ItemType == C2.ItemType, C1.ItemType: Equatable {

// 检查两个容器含有相同数量的元素 比较对象必须遵循 Equatable
if someContainer.count != anotherContainer.count {
return false
}

// 检查每一对元素是否相等
for i in 0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
}
}

// 所有元素都匹配,返回 true
return true
}

var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")

var arrayOfStrings = ["uno", "dos", "tres"]

if allItemsMatch(stackOfStrings, arrayOfStrings) {
print("All items match.")
} else {
print("Not all items match.")
}
// 打印 “All items match.”

where还可以应用于扩展中

1
2
3
4
5
6
7
8
9
// 因为 在扩展的方法内使用的 == 符合,着泛型类型必须遵循 Equatable
extension Stack where Element: Equatable {
func isTop(_ item: Element) -> Bool {
guard let topItem = items.last else {
return false
}
return topItem == item
}
}

自动引用计数

Swift和Java,JavaScript一样无需手动释放内存,它会自动释放不会被使用的内存。自动引用计数(ARC)被用来跟着和管理类实例的内存状态,值类型的数据内存不会被ARC管理。

对于一个引用类型的类实例,ARC会统计这个实例被其它实例或变量引用的次数,当发现这个实例引用次数为0时,则表示它不会被使用了,最后会被系统释放掉。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 定义一个类Person
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}

// 定义三个可选项 Person类型的变量
var reference1: Person?
var reference2: Person?
var reference3: Person?

// 实例话 Person 这时这个实力引用计数为1
reference1 = Person(name: "John Appleseed")
// 打印 "John Appleseed is being initialized"

reference2 = reference1 // 再次被引用,该实例引用计数为2
reference3 = reference1 // 再次被引用,该实例引用计数为3

reference1 = nil // 解除一个引用关系,这时引用计算为2
reference2 = nil // 解除一个引用关系,这时引用计算为1

reference3 = nil // // 解除一个引用关系,这时引用计算为0。释放实例内存,触发析构函数
// 打印 "John Appleseed is being deinitialized"

强引用

在实际代码中,两个实例之间或实例和闭包之间产生循环引用情况,这种引用被称为强引用,必须通过指明它们之间的引用关系才能使得ARC准确的统计引用计数释放内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
* 定义两个类型,Person和 Apartment。这两个类型内部的属性会互相引用。
*/
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment? // 引用 Apartment 实例
deinit { print("\(name) is being deinitialized") }
}

class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
var tenant: Person? // 引用 Person
deinit { print("Apartment \(unit) is being deinitialized") }
}

var john: Person? // Person类型变量
var unit4A: Apartment? // Apartment类型变量


john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")

上面的例子,两个实例还没有产生循环引用。ARC关系如下图。

1
2
john!.apartment = unit4A // john内部属性 apartment 引用了 unit4A
unit4A!.tenant = john // unit4A内部属性 tenant 引用了 john

上面代码使得两个实例之间产生了循环强引用。ARC关系如下图。

1
2
3
// 将Apartment、Person实例变量设置为nil,表示该变量引用的实例不在需要
john = nil
unit4A = nil

上面的代码表示ApartmentPerson实例不在需要,内存可以被释放。但实际上ARC并不会释放这两个实例的内存,因为ApartmentPerson引用计数不为0。

如上图展示的,两个实例内部属性的循环引用导致引用计数不为0。

弱引用(weak)和 无主引用(unowned)

可以用 弱引用无主引用 这两个方式来说明循环引用之间的关系。这样ARC能够知道实例内存在什么时候可以 被释放。

  • 弱引用(weak):用weak关键字声明可选型的var变量,该变量引用的实例被设为nil时,该变量也会被设置为nil。以切断其与实例的引用关系。
1
2
3
4
5
6
7
8
9
10
11
12
13
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}

class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
weak var tenant: Person? // 弱引用 修饰的应该是可选的变量
deinit { print("Apartment \(unit) is being deinitialized") }
}

建立强引用:

1
2
3
4
5
6
7
8
var john: Person?
var unit4A: Apartment?

john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")

john!.apartment = unit4A
unit4A!.tenant = john

此时变量johnunit4A的关下图:

tenant变量是弱引用,它不会阻止john实例内存被释放。

1
2
john = nil
// 打印 "John Appleseed is being deinitialized"

  • 无主引用(unowned):和弱引用不同的是,无主引用变量必须有值并且具有无主引用的实例必须比无主引用的实例生命周期长或相同。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
deinit { print("\(name) is being deinitialized") }
}

// CreditCard实例 属性customer必须拥有一个实例 所以可以设置为常量
// 而 Customer实例生命周期和 CreditCard 应该相同或更短
// 所以 可以用 unowned修饰 customer
class CreditCard {
let number: UInt64
unowned let customer: Customer
init(number: UInt64, customer: Customer) {
self.number = number
self.customer = customer
}
deinit { print("Card #\(number) is being deinitialized") }
}

var john: Customer?
john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)

实例CustomerCreditCard引用关系:

Customer持有对CreditCard强引用,而CreditCard持有Cunstomer的无主引用。当John变量不在引用 Customer实例,ARC会将Customer实例内存释放掉。

1
2
3
john = nil
// 打印 "John Appleseed is being deinitialized"
// 打印 "Card #1234567890123456 is being deinitialized"

由于再也没有指向Customer实例的强引用,该实例将销毁了。

闭包的强引用

闭包也是引用类型,如果一个类实例有属性被赋值给闭包,而闭包使用了该类实例其他属性。这样闭包和该类实例就产生了强引用。即使该类实例被赋值nil,该实例内存也不会释放。

Swift提供了定义捕获列表用于解决闭包强引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class HTMLElement {

let name: String
let text: String?

lazy var asHTML: (Void) -> String = {
// 因为 引用的实例和闭包生命周期相同,所以用 unowned修饰 self
[unowned self] in
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}

init(name: String, text: String? = nil) {
self.name = name
self.text = text
}

deinit {
print("\(name) is being deinitialized")
}

}

var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// 打印 "<p>hello, world</p>"

paragraph = nil
// 打印 "p is being deinitialized"

错误处理

在运行时过程中,代码可能因为不可预知问题导致怎么系统奔溃。为了避免这种情况发生,我们可以指明(throws)代码中可能发生错误,在代码中抛出(throw)错误,并捕获(do-catch)发生错误后处理方式以避免系统奔溃。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/*
* 根据业务情况,先用枚举定义好可能发生错误种类。
* 该枚举需要遵循Error协议,以表明该类型可以被用于错误处理
*/
enum VendingMachineError: Error {
case invalidSelection //选择无效
case insufficientFunds(coinsNeeded: Int) //金额不足
case outOfStock //缺货
}

struct Item {
var price: Int
var count: Int
}

class VendingMachine {
var inventory = [
"Candy Bar": Item(price: 12, count: 7),
"Chips": Item(price: 10, count: 4),
"Pretzels": Item(price: 7, count: 11)
]
var coinsDeposited = 0
func dispenseSnack(snack: String) {
print("Dispensing \(snack)")
}

// 用throws声明该方法可能抛出错误
func vend(itemNamed name: String) throws {
// 如果 inventory不包含 name指定数据,则抛出错误
guard let item = inventory[name] else {
throw VendingMachineError.invalidSelection
}
// 如果 item长度为0,则抛出错误
guard item.count > 0 else {
throw VendingMachineError.outOfStock
}

guard item.price <= coinsDeposited else {
throw VendingMachineError.insufficientFunds(coinsNeeded: item.price - coinsDeposited)
}

coinsDeposited -= item.price
var newItem = item
newItem.count -= 1
inventory[name] = newItem

print("Dispensing \(name)")
}
}

let favoriteSnacks = [
"Alice": "Chips",
"Bob": "Licorice",
"Eve": "Pretzels",
]
func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
let snackName = favoriteSnacks[person] ?? "Candy Bar"
// 在可能抛出错误的方法前需要 使用try声明
try vendingMachine.vend(itemNamed: snackName)
}

var vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8
/*
* 通过 do-catch捕获错误
* 并根据抛出错误的类型进行处理
*/
do {
try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
} catch VendingMachineError.invalidSelection {
print("Invalid Selection.")
} catch VendingMachineError.outOfStock {
print("Out of Stock.")
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
}
// 打印 “Insufficient funds. Please insert an additional 2 coins.”

throws可以用于方法、函数、构造器。如果有返回值,throws应放在箭头符号(->)前面。

1
2
func canThrowErrors() throws -> String
func cannotThrowErrors() -> String

使用try?通过将错误转换成一个可选值来处理错误。

1
2
3
4
5
6
7
8
9
10
11
12
func someThrowingFunction() throws -> Int {
// ...
}
// 如果 someThrowingFunction 抛出错误,则x、y将是nil。否则将是方法的返回值。
let x = try? someThrowingFunction()

let y: Int?
do {
y = try someThrowingFunction()
} catch {
y = nil
}

如果 你确定某个可抛出错误的方法不会抛出错误,你可以使用try!声明它。

指定清理操作 defer

我们会用breakreturn提前退出当前作用域,或抛出一个异常。如果一些操作需要在退出该作用域前被执行,可以将它们放在defer语句中。

1
2
3
4
5
6
7
8
9
10
11
12
13
func processFile(filename: String) throws {
if exists(filename) {
let file = open(filename)
// 如果有异常退出该作用域,则在退出之前执行 defer内的操作
defer {
close(file)
}
while let line = try file.readline() {
// 处理文件。
}
// close(file) 会在这里被调用,即作用域的最后。
}
}

如果一个作用域有多个defer语句,则defer按声明倒序一次执行。最后声明的defer语句先执行。

类型判断(is)和转换(as)

通过is操作符可以判断操作符左边的实例类型是否属于操作符右边的类型,如果是返回true否则返回false

1
2
3
4
5
6
7
8
9
10
11
class Person {
var name: String
var age: Int
init (_ name:String,_ age:Int) {
self.name = name
self.age = age
}
}
let echo = Person("Echo",28);
print(echo is Person)
// true

如果一个实例属于一个子类型,通过as操作符可以将它转换成该类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// 基类
class MediaItem {
var name: String
init(name: String) {
self.name = name
}
}

// 子类
class Movie: MediaItem {
var director: String
init(name: String, director: String) {
self.director = director
super.init(name: name)
}
}

// 另一个子类
class Song: MediaItem {
var artist: String
init(name: String, artist: String) {
self.artist = artist
super.init(name: name)
}
}

let library = [
Movie(name: "Casablanca", director: "Michael Curtiz"),
Song(name: "Blue Suede Shoes", artist: "Elvis Presley"),
Movie(name: "Citizen Kane", director: "Orson Welles"),
Song(name: "The One And Only", artist: "Chesney Hawkes"),
Song(name: "Never Gonna Give You Up", artist: "Rick Astley")
]
// 数组 library 的类型被推断为 [MediaItem]

var movieCount = 0
var songCount = 0

for item in library {
// 判断实例类型是否属于 Movie 或 Song
if item is Movie {
movieCount += 1
} else if item is Song {
songCount += 1
}
}

print("Media library contains \(movieCount) movies and \(songCount) songs")
// 打印 “Media library contains 2 movies and 3 songs”

for item in library {
// 进行类型转换 as? 返回一个可选型
if let movie = item as? Movie {
print("Movie: '\(movie.name)', dir. \(movie.director)")
} else if let song = item as? Song {
print("Song: '\(song.name)', by \(song.artist)")
}
}

// Movie: 'Casablanca', dir. Michael Curtiz
// Song: 'Blue Suede Shoes', by Elvis Presley
// Movie: 'Citizen Kane', dir. Orson Welles
// Song: 'The One And Only', by Chesney Hawkes
// Song: 'Never Gonna Give You Up', by Rick Astley

Any 和 AnyObject 的类型转换

  • Any可以表示任何类型,包括函数类型。
  • AnyObject 可以表示任何类类型的实例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
var things = [Any]() // 被定义为接受任何类型项

things.append(0)
things.append(0.0)
things.append(42)
things.append(3.14159)
things.append("hello")
things.append((3.0, 5.0))
things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))
things.append({ (name: String) -> String in "Hello, \(name)" })

// 迭代 things 通过switch 匹配ting类型.用 as操作符 做类型匹配条件
for thing in things {
switch thing {
case 0 as Int:
print("zero as an Int")
case 0 as Double:
print("zero as a Double")
case let someInt as Int:
print("an integer value of \(someInt)")
case let someDouble as Double where someDouble > 0:
print("a positive double value of \(someDouble)")
case is Double:
print("some other double value that I don't want to print")
case let someString as String:
print("a string value of \"\(someString)\"")
case let (x, y) as (Double, Double):
print("an (x, y) point at \(x), \(y)")
case let movie as Movie:
print("a movie called '\(movie.name)', dir. \(movie.director)")
case let stringConverter as (String) -> String:
print(stringConverter("Michael"))
default:
print("something else")
}
}

// zero as an Int
// zero as a Double
// an integer value of 42
// a positive double value of 3.14159
// a string value of "hello"
// an (x, y) point at 3.0, 5.0
// a movie called 'Ghostbusters', dir. Ivan Reitman
// Hello, Michael

内存安全

一般情况下一块内存空间访问是在瞬时间完成的,这个时间点上只可能有一个操作行为。如果在这个时间点上发生了多个操作,则会导致内存冲突。这种情况是不安全的内存行为。

内存访问冲突有三类型:

  1. 两个操作同时访问,并且又一个访问是写操作。
  2. 当两个访问是存储型内存时。
  3. 两个访问在时间上有重叠时。

最容易导致访问冲突语法场景是:In-Out 参数的访问冲突,方法里 self 的访问冲突,属性的访问冲突。

In-Out 参数的访问冲突

函数内部可以通过参数和全局变量访问同一块内存。很容易造成访问冲突。

1
2
3
4
5
6
7
8
var stepSize = 1

func increment(_ number: inout Int) {
number += stepSize
}

increment(&stepSize)
// 错误:stepSize 访问冲突

上面例子中,numberstepSize访问了同一块内存。

方法里 self 的访问冲突

在方法内访问自身就可能造成内存冲突。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Player结构体
struct Player {
var name: String
var health: Int
var energy: Int

static let maxHealth = 10
mutating func restoreHealth() {
health = Player.maxHealth
}
}

/*
* 扩展Player 使它可以与同类型实例 进行数据变化
*/
extension Player {
// 接受同类型实例参数
mutating func shareHealth(with teammate: inout Player) {
// 将参数属性health 和自身health 进行数据处理
balance(&teammate.health, &health)
}
}

如果传递给 shareHealth参数实例和调用shareHealth方法的实例不是同一个是正常的。

1
2
3
4
5
6
var oscar = Player(name: "Oscar", health: 10, energy: 10)
var maria = Player(name: "Maria", health: 5, energy: 10)
oscar.shareHealth(with: &maria) // 正常

oscar.shareHealth(with: &oscar)
// 错误:oscar 访问冲突

假如传递给 shareHealth参数实例和调用shareHealth方法的实例是同一实例,则会导致内存冲突。

属性的访问冲突

结构体,元组,枚举类型都是独立的值类型,对它们一个 属性的访问其实就是对整体的访问。这样情况很容易导致内存冲突。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct Player {
var name: String
var health: Int
var energy: Int

static let maxHealth = 10
mutating func restoreHealth() {
health = Player.maxHealth
}
}
// 对于一个存储在全局变量里的结构体属性的写访问重叠了
var holly = Player(name: "Holly", health: 10, energy: 10)
balance(&holly.health, &holly.energy) // 错误

// 对于元组元素的写访问都需要对整个元组发起写访问
var playerInformation = (health: 10, energy: 20)
balance(&playerInformation.health, &playerInformation.energy)

func someFunction() {
// 假如结构体存在本地变量而非全局变量,编译器就会可以保证这个重叠访问时安全的
var oscar = Player(name: "Oscar", health: 10, energy: 10)
balance(&oscar.health, &oscar.energy) // 正常
}