Swift高级

本文介绍Swift语言的枚举、结构体、类、协议和扩展等相关知识。

枚举

通过enum语法来定义一个枚举:

1
2
3
4
5
6
enum Direction {
case north
case south
case east
case west
}
与C、Java等语言的枚举不同,Swift的枚举没有底层的整型,如果想让Swift的枚举具有相同的功能,可以给枚举设置一个原始值。这里值得注意的是每个case子句的原始值不能相同。
1
2
3
4
5
6
7
8
9
10
11
12
// 为每个case子句赋值,注意原始值不能相同
enum Direction: Int {
case north = 1
case south = 2
case east = 3
case west = 4
}

// 我们也没有必要像上面那样为每个case子句赋值,Swift支持隐形赋值
enum Direction: Int {
case north = 1, south, east, west
}
除了原始值,枚举还支持关联值,关联值相当于是枚举中的额外信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 我们定义一个Person枚举,fullName和shortName都具有一个String的关联值
enum Person {
case fullName(String)
case shortName(String)
}

var p = Person.fullName("gcfeng")
// 编译器可以推断出p是Person类型,所以这里省略了Person前缀
p = .shortName("gc")

// 使用switch语句可以取出关联值
switch p {
case .fullName(let name):
print(name)
case .shortName(let name):
print(name)
}
枚举还支持递归,不过要使用递归枚举,我们需要使用indirect关键字来告诉编译器。因为Swift编译器需要知道每个实例占据多少空间,而递归枚举是无限循环的,无法计算所需内存空间,indirect关键字告诉编译器将枚举的数据放到一个指针指向的地方。
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
// 定义一个递归枚举
enum Expression {
case number(Int)
indirect case add(Expression, Expression)
indirect case multiply(Expression, Expression)
}

// 如果所有成员都是可递归的,我们可以将indirect提到enum前面
indirect enum Expression {
case number(Int)
case add(Expression, Expression)
case multiply(Expression, Expression)
}

// 定义一个函数来使用递归枚举
func evaluate(_ expression: Expression) -> Int {
switch expression {
case let .number(value):
return value
case let .add(left, right):
return evaluate(left) + evaluate(right)
case let .multiply(left, right):
return evaluate(left) * evaluate(right)
}
}

let a = Expression.number(1)
let b = Expression.number(2)
let sum = Expression.add(a, b)
evaluate(sum) // 输出:3

结构体和类

结构体和类都提供了面向对象编程的抽象封装能力。在Swift中,结构体的使用要简单的多,如果不涉及继承等复杂场景,推荐使用结构体。使用struct来定义结构体,使用class来定义类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 定义结构体
struct Resolution {
var width = 0
var height = 0
}

// 定义类
class VideoMode {
var resolution = Resolution()
var name: String?
}

// 初始化实例
var r = Resolution()
var v = VideoMode()
我们可以给结构体和类定义类型方法,类型方法是类型本身来调用,这个有点像Java中的静态方法。但是结构体和类对于类型方法的声明关键字并不一样。结构体使用static来定义,类使用class来定义。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 定义结构体
struct Resolution {
var width = 0
var height = 0

static func printMsg() {
print("resolution")
}
}

// 定义类
class VideoMode {
var resolution = Resolution()
var name: String?

class func printMsg() {
print("videoMode")
}
}

// 使用类型本身来调用
Resolution.printMsg()
VideoMode.printMsg()
对于VideoMode类而言,如果不希望子类重写printMsg方法,可以使用final class来修饰printMsg,或者使用static来修饰printMsg

结构体中的方法默认不能修改属性值,比如上面Resolution中的printMsg。如果希望结构体中的方法能修改属性值,需要在方法声明中加上mutating关键字。

1
2
3
4
5
6
7
8
9
10
11
struct Person {
var age = 10
var name = "gc"

mutating func addAge() {
self.age += 1
}
}

var p = Person()
p.addAge()
与JavaScript类似的,Swift将属性分为存储属性和计算属性。存储属性可用于类和结构体,计算属性可用于枚举、结构体和类。存储属性和计算属性的区别在于,存储属性实际保存值,计算属性不实际保存值。值得注意的是,Swift为属性增加了willSetdidSet观察器,当修改属性值时,会触发属性观察器,应用此特性,可以很方便的实现前端的MVVM功能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct Person {
// 存储属性
var name = "gc"
// 存储属性,同时定义了didSet观察器
// 当age完成修改时,会触发didSet函数
var age = 20 {
didSet(oldAge) {
print("The age has changed to \(age) from \(oldAge)")
}
}
// 计算属性
var description: String {
get {
return "\(name) is \(age) years old"
}
}
}

var p = Person()
p.age = 10
print(p.description)

构造过程和析构过程

我们之前定义的结构体和类,都默认给属性赋了初始值,这是初始化的一种方式。实际上,我们可以通过构造器来给属性赋值。使用init来定义构造器,语法如下:

1
2
3
4
// 注意这里没有使用func来修饰
init(parameter: SomeType) {
// 初始化代码
}
Swift为类的构造过程提供了两种构造器,分别是指定(designated)构造器和便利(convenience)构造器。这两种构造器有几个使用原则:

  • 一个指定构造器必须调用它直系父类的一个指定构造器
  • 一个便利构造器必须调用这个类自身的另一个构造器
  • 一个便利构造器最终一定会调用一个指定构造器

Swift通过便利构造器给一些属性赋默认值,从而达到简化参数传递个数的目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person {
var name: String
var age: Int

init(name: String, age: Int) {
self.name = name
self.age = age
}

convenience init(name: String) {
self.init(name: name, age: 10)
}
}

var p = Person(name: "gc")
析构过程是构造过程的反面,是在类的实例没用之后将其清除出内存的过程,只适用于类。每个类最多只能有一个析构器,析构器的定义如下:
1
2
3
deinit {
// 释放额外的引用资源
}

协议

协议能让你定义类型需要满足的接口,满足某个协议的类型被称为符合这个协议。协议不仅能定义符合该协议的类型必须提供的属性和方法,自己还能作为类型使用,变量、函数参数和返回值都可以把协议作为类型。结构体、枚举、类都可以符合协议。一个类型可以符合多个协议,如果类有父类,父类的名字在前,然后再跟协议。Swift中的协议与Java语言的接口很像。Mac和iOS应用通常都把数据的展现和提供数据的源分离。利用协议,我们可以很好的做到这一点。

使用protocol来定义一个协议:

1
2
3
4
5
6
7
8
9
10
11
// 定义了一个协议DataSource
protocol DataSource {
// 符合DataSource协议的类型必须要有numberOfRows和numberOfColumns两个属性
// DataSource协议并不要求对这两个属性可写,所以在后面写上了{ get },表示只读就行
var numberOfRows: Int { get }
var numberOfColumns: Int { get }

func label(forColumn column: Int) -> String

func itemFor(row: Int, column: Int) -> String
}
下面来定义结构体使用上述协议:
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
struct Person {
let name: String
let age: Int
}

// 让Department符合DataSource协议
struct Department: DataSource {
let name: String
var people: [Person]()

init(name: String) {
self.name = name
}

mutating func add(_ person: Person) {
people.append(person)
}

// Mark: DataSource
// 实现协议定义的属性和方法
var numberOfRows: Int {
return people.count
}

var numberOfColumns: Int {
return 2
}

func label(forColumn column: Int) -> String {
switch column {
case 0: return "Name"
case 1: return "Age"
default: fatalError("Invalid column!")
}
}

func itemFor(row: Int, column: Int) -> String {
let person = people[row]
switch column {
case 0: return person.name
case 1: return String(person.age)
default: fatalError("Invalid column!")
}
}
}

var department = Department(name: "Engineering")
department.add(Person(name: "a", age: 20))

扩展

扩展可以给一个现有的类、结构体、枚举,还有协议添加新的功能。Swift的扩展可以:

  • 添加计算属性
  • 添加新初始化方法
  • 使类型符合协议
  • 添加新方法
  • 添加嵌入类型

下面我们扩展已有的Double类型:

1
2
3
4
5
6
7
// 扩展Double类型,添加一个km属性
extension Double {
var km: Double { return self / 1000.0 }
}

let d = 100.0
print(d.km)
值得注意的是扩展不允许为类型添加存储属性。

泛型

泛型是面向对象编程中实现多态的工具,Swift中的泛型与Java等语言的泛型非常类似。比如定义一个类的泛型:

1
2
3
4
5
6
// 定义了一个Stack数据结构,Element是占位类型
// 在实例化类型的时候会特化具体类型,比如 var a = Stack<Int>()
struct Stack<Element> {
var items = [Element]()
...
}
为了能对占位类型有所假设,类似Java为泛型定义的上下界,Swift可以为泛型定义两种类型约束:一种是类型必须是给定类的子类,还有一种是类型必须符合一个协议(或者一个协议组合)。
1
2
3
4
// 让占位类型T符合Equatable协议
func checkIfEqual<T: Equatable>(_ first: T, _ second: T) -> Bool {
return first == second
}