References: The Swift Programming Language

某程序员对书法十分感兴趣,退休后决定在这方面有所建树。于是花重金购买了上等的文房四宝。一日,饭后突生雅兴,一番磨墨拟纸,并点上了上好的檀香,颇有王羲之风范,又具颜真卿气势,定神片刻,泼墨挥毫,郑重地写下一行字:hello world


1
print("Hello, World")

Swift 的基础语法

值与类型

定义变量用 var,常量用 let。常量只能赋值一次,但不用必须在声明的时候去赋值。

1
2
3
4
var myVariable = 42
myVariable = 50

let myConstant = 42

编译器会自动推断常量和变量的类型,但是如果推断不出来(比如说没有初始值等),就需要声明类型。

1
2
3
4
let helloTalk : String
helloTalk = "helloTalk"

let helloTalk = "helloTalk"

Swift的值不会隐式被转为其他类型

1
2
3
4
5
let widthFloat = 93.33          // 自动推断 为 Double
let width : Int = widthFloat // 把 Double 赋值给 Int,会报错
let widthLabel = label + String(width)
// 把值转换成字符串还可以这样: \(ValueName)
let widthString = "width: \(width)."

创建、定义一个数组或者字典

1
2
3
4
5
let emptyArray = [String]()
let emptyDictionary = [String: Float]()
// 或者
let emptyArray = []
let emptyDictionary = [:]

数组和字典都是集合类型,对于这种类型的 letvar 修饰并非是像普通值的可否赋值。
比如说,用 let 修饰的数组是不能添加和移除数组中的元素,数组中的元素个数、位置均不可变,但是用 var 修饰的数组可以添加/删除元素。

枚举

枚举是为一组相关的值定义了一个共同类型,在 Swift 中,枚举是“一等公民”。枚举成员的原始值不仅可以是整形,还可以是字符、字符串、浮点型;此外,枚举还支持属性、方法、甚至是构造函数、扩展和遵守协议。

enum 关键词来创建枚举:

1
2
3
4
5
6
7
8
9
10
11
12
enum Direction {
case north
case south
case east
case west
}
// 或者用逗号分隔写作一行:
enum Direction {
case north, south, east, west
}

let direction: Direction = .west

原始值

1
2
3
4
5
6
7
8
9
enum Direction: String {
case north = "北"
case south = "南"
case east = "东"
case west = "西"
}

let north = Direction.north // the sanme as : let north = Direction(rawValue: "北")
north.rawValue // "北"

关联值

Swift 的枚举可以存储各个类型的关联值,而且每个成员的类型可以都不一样。所以对于一个网络请求可以有这样的抽象:一个网络请求的结果,可以是成功或者失败的,如果成功则返回的是我们想要的数据,不成功返回错误原因,那么可以写成这样:

1
2
3
4
enum Result<Value> {
case success(Value)
case failure(Error)
}

Value 是泛型语法,可以是任何你需要的类型。

控制流

for-in

1
2
3
4
5
6
7
8
9
10
11
12
13
let persons = ["person1","person2"]
for personString in persons {
print(personString)
}
// 遍历字典
let dict = [
"name" : "Joke",
"age" : 16
] as [String : Any]

for (key,value) in dict {
print("\(key) : \(value)")
}

if-else

1
2
3
4
5
if 2>1 {
print("2 大于 1")
}else{
print("2 还是大于 1 啊")
}

if 后面必须是布尔表达式,如果是一个值的话不会隐式的与 0 比较。

switch

1
2
3
4
5
6
7
8
9
10
11
let vegetable = "red pepper"
switch vegetable {
case "celery":
print("Add some raisins and make ants on a log.")
case "cucumber", "watercress":
print("That would make a good tea sandwich.")
case let x where x.hasSuffix("pepper"):
print("Is it a spicy \(x)?")
default:
print("Everything tastes good in soup.")
}

switch 不仅支持基本数据类型。另外Swift中的switch语法可以省略break。但是不能省略 default,会报 Switch must be exhaustive, consider adding a default clause 的编译错误。

while

1
2
3
4
5
6
7
8
9
var i = 1
while i<100 {
i += 1
}
// 或者
var i = 1
repeat{
i += 1
}while i < 100

Swift 的函数和闭包

函数的关键字是 func ,函数定义的格式是:

swift-function

1
2
3
func funcName(para:paraType) -> returnType{
// code
}

函数的参数标签

其中参数的那部分的详细结构是用小括号括起来,参数名,冒号,参数类型: (number:Int)
在默认情况下,函数的参数标签使用参数名,或者用 _ 不使用参数标签,也可以自定义标签,我们定义一个奇葩的函数:

1
2
3
func 求和(数字1 num1:Float, 数字2 num2:Float) -> Float {
return num1 + num2
}

求和 就是方法名,数字1 就是自定义的参数标签。调用时会显示标签:

1
let sum = 求和(数字1: 2, 数字2: 4)

函数返回多个值

swift 还可以用元组返回多个返回值:

1
2
3
4
5
6
7
8
9
10
11
12
func compare(numarray:([Int])) -> (min:Int, max:Int) {
var min = numarray[0]
var max = numarray[0]
for num in numarray {
if num > max {
max = num
}else if num < min{
min = num
}
}
return (min, max)
}

调用时获取返回值:

1
compare(numarray: [1, 2, 3, 4, 5]).max

函数嵌套函数

swift 语法中函数可以嵌套函数,用于分割太长或者太复杂的函数:

1
2
3
4
5
6
7
8
9
// 不要在意逻辑,只是为了示例一下。。。
func sumWithArray(numArray:([Int])) -> Int{
var sum = 0
func add(num1:Int, num2:Int) -> Int{
return num1 + num2
}
sum = add(num1: numArray[0], num2: numArray[1])
return sum
}

返回一个函数

函数还可以用一个函数做为返回值

1
2
3
4
5
6
func makeMethod() -> ((Int)->(Int)) {
func addOne(num:Int)->(Int){
return num+1
}
return addOne
}

函数调用:

1
2
3
4
5
print("makeMethod()(1993): ",makeMethod()(1993))
// makeMethod()(1993): 1994
/**
makeMethod() 返回的是一个函数,继续传入参数 1993,最后返回 1994
*/

传入一个函数

函数可以把一个函数当做返回值返回,也可以当做一个参数来传入:

1
2
3
func sumOfMaxMin(numarray:([Int]),compare:(_ numarray:([Int]))->(min:Int, max:Int)) -> (Int) {
return compare(numarray).max + compare(numarray).min
}

可以看到, sumOfMaxMin 函数有两个参数:numarray:([Int])compare:(_ numarray:([Int]))->(min:Int, max:Int) 。其中 compare 是一个函数。

在调用的时候:

1
var sumOfMaxMinValue = sumOfMaxMin(numarray: [1, 2, 3, 4, 5],compare: compare)

compare 是上个例子中的函数。当然,我们也可以不传入一个现成已经定义和实现的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var sumOfMaxMinValue = sumOfMaxMin(numarray: [1, 2, 3, 4, 5]) { (numarray:([Int])) -> (min: Int, max: Int) in
var min = numarray[0]
var max = numarray[0]

for num in numarray {
if num > max {
max = num
}else if num < min{
min = num
}
}

return (min, max)
}

函数是一种特殊的闭包

大家伙看到这里,肯定会一拍大腿:哎呦,这玩意不就是闭包嘛!

(The Swift Programming Language)函数实际上是一种特殊的闭包:它是一段能之后被调取的代码。闭包中的代码能访问闭包所建作用域中能得到的变量和函数,即使闭包是在一个不同的作用域被执行的

我们可以使用{}来创建一个匿名闭包。使用in将参数和返回值类型声明与闭包函数体进行分离。

1
2
3
4
5
6
let numArray:([Int]) = [1, 2, 3, 4, 5] 
var newNumArray:([Int]) = numArray.map({
(num:Int) -> Int in
let newNum = num * 3
return newNum
})

闭包的简写

如果闭包的类型已知,那么可以省略参数和返回值的类型

1
2
3
4
5
6
let numArray:([Int]) = [1, 2, 3, 4, 5]
var newNumArray:([Int]) = numArray.map({
num in
let newNum = num * 3
return newNum
})

单个语句闭包会把它语句的值当做结果返回

1
2
3
4
5
let numArray:([Int]) = [1, 2, 3, 4, 5]
var newNumArray:([Int]) = numArray.map({
num in
num * 3
})

如果把上面的闭包写成一行的话

1
2
let numArray:([Int]) = [1, 2, 3, 4, 5]
var newNumArray:([Int]) = numArray.map({num in num * 3})

我们可以通过参数位置而不是参数名字来引用参数,那么上面的代码就变成这样

1
2
let numArray:([Int]) = [1, 2, 3, 4, 5]
var newNumArray:([Int]) = numArray.map({$0 * 3})

当一个闭包是传给函数的唯一参数,我们可以完全忽略括号

1
2
let numArray:([Int]) = [1, 2, 3, 4, 5]
var newNumArray:([Int]) = numArray.map{$0 * 3}

Swift 的类和对象

类的定义

类定义的关键字是 class,我们用 class + 类名 + “:“ + 父类,比如定义一个 Person 类,类中去声明和定义变量和函数:

1
2
3
4
5
6
class Person: NSObject {
var name: String
public func sayHello(){
print("Hello ~")
}
}

类的扩展

1
2
3
extension Person {  // 给人加一个飞的功能...
func fly() {}
}

属性

存储属性

存储属性就是存储在类或者结构体一个实例里的一个常量和变量,用 var 或者 let 修饰。

但是如果一个结构体实例被声明为常量,那么即便这个结构体的某个属性是变量,也是不能去改变的:

1
2
let point = CGPoint(x: 1, y: 1)
point.x = 10 // 编译是不会通过的

延迟存储属性

1
2
3
4
5
lazy var nameLabel:UILabel = {
let label = UILabel()
// ...
return label
}()

其实就是把一个立即执行的闭包的返回值赋值给属性,以达到懒加载的目的。

属性观察器

willSet 在新的值被设置之前调用
didSet 在新的值被设置之后立即调用
需要注意的是:当为存储型属性设置默认值或者在构造器中为其赋值时,它们的值是被直接设置的,不会触发任何属性观察者。

计算属性

计算属性提供了一个 getter 和一个可选的 setter 。属性的 gettersetter 的关键词是 getset ,在 setter 中新值是 newValue

1
2
3
4
5
6
7
8
9
class Person: NSObject {
var birthYear: Int
var age : Int{
set{
self.age = newValue
self.birthYear = 2016 - newValue
}
}
}

只读计算属性

当一个属性只有 getter 而没有 setter 时,那么它就是一个只读计算属性

类对象的实例和访问:

1
2
var person = Person()
person.age = 10

类的构造和析构

我们可以给 Person 自定义一个构造函数,构造函数中需要给所有的存储型属性一个赋值或者默认值:

1
2
3
init(name: String) {
self.name = name
}

这样我们就可以用 name 来实例化一个对象: var person = Person(name:"Tom")

如果所有属性都有默认值,我们没有自定义的构造,系统会生成一个默认的构造函数:var person = Person()
相应的,如果我们自定义了一个构造函数,那么系统便不会为该类生成默认的构造函数。
但是,我们可以把自定义的构造函数写到类的扩展(extension)里,而不是类的原始定义里面。

与构造函数对应的是析构函数:deinit

指定构造器和便利构造器
指定构造器里面会初始化类实例所有的属性,所以为了保证继承的属性也能被初始化,子类的指定构造器都会调用父类的指定构造器,而类的便利构造器总是会调用本类的指定构造器。
也就是说:指定构造器需要向上调用,便利构造器需要横向调用

所以,我们可以把父类的指定构造函数重写成便利构造,却不能重写父类的便利构造。即便重写了父类的便利构造函数,但是由于我们不能直接调用父类的便利构造函数,所以不用给函数以 override 修饰。

必要构造器 如果某个构造函数被 required 修饰,那么该函数就是必要构造函数,子类继承该类时都必须要实现改构造函数。在子类重写父类的构造函数的时候,也要加 required 来修饰,以确定继承链上子类的子类也遵守。

除了构造和析构函数,类还有实例的私有函数、公共函数和静态函数。

类、函数的访问控制(访问、继承/重载 权限)

Swift 中的访问控制有 模块源文件 两个概念。用 “import” 导入的就是模块。

对于类而言的修饰词与权限:

修饰词 权限
open 修饰的类可以随意继承与访问
public 修饰的类只能在本模块内被继承,但是可以随便访问
internal 默认 - 模块内拥有访问权限
fileprivate 是文件外部不能被访问
private 是文件内部不能被访问
final 是文件内部也不能被继承

当然了,对于一个 internal 的类,其属性和方法的级别是不会超出类本身的,比如不可能是 public

函数也是同样的修饰词和权限,只是类的继承对应函数的重载权限。
函数除了以上几个,常用的修饰词还有 staticoverride

static修饰的是类方法。override修饰的是重写父类的方法。

Swift 中的类和结构体

Swift 中的结构体的能力被大大加强,不仅可以拥有属性,还以有方法、构造函数、甚至是扩展和遵守协议。这样的结构体和类有很多相同点:

  • 属性:存储数据
  • 方法:提供一些功能
  • 下标:可以使用下标语法
  • 构造器:生成初始化值
  • 扩展:增加功能
  • 协议:提供某种通用功能

当然,类和结构体也有很多不同的地方,类还有许多独有的附加功能:

  • 继承:一个类可以继承另一个类的特征
  • 类型转换:运行时检查和解释一个类实例的类型
  • 析构器:一个类实例释放任何其所被分配的资源
  • 引用计数:对一个类的多次引用

结构体会提供一个默认的构造函数,这个构造函数是结构体所有的属性分别作为参数来构建:

1
2
3
4
5
struct MyPoint {
var x = 0
var y = 0
}
let point:MyPoint = MyPoint(x: 1, y: 2)

结构体和枚举都是值类型,值类型在赋值(给变量或者常量)和传递(作为参数给一个函数)的时候都会被拷贝,值类型实例的值属性也会被拷贝。
Swift 中的整型、浮点型、布尔型、字符串、字典、数组都是值类型,底层都是由结构体来实现的。

类是引用类型,引用类型在赋值和传递的时候,内容并不会被拷贝。因此赋值的实例和被赋值的实例其实是一份内容,内容在内存中也是一份。

值类型和引用类型的区别在于,值类型在赋值和传递的时候是深拷贝,而引用类型是浅拷贝。
深拷贝就是把内存地址中存放的内容也拷贝一份内存中的内容就会有两份;而浅拷贝只是拷贝了内存的地址,内存中的内容还是只有一份。

值和引用类型的区别

但需要注意的是,在 Swift 中,并不是值类型一旦被赋值和传递的时候就会被拷贝一份,只有当需要的时候,比如被赋值的实例去改变内容的时候才会真正的去拷贝。

那么,我们到底如何选择结构体或者类呢?如果你只是用来做以下功能是可以选择结构体:

  • 只是用来封装一些相关的数据
  • 这些数据被赋值或者传递的时候会被拷贝一份
  • 不需要被继承

比如 CGPointCGRectCGSize等都是结构体。