Swift 快速入门 (上)

本文介绍 Swift 语言的基础内容, 面向已经熟练掌握某项开发语言的开发者, 因此内容力求简洁. "Swift 快速入门" 上篇包括常用的数据类型、控制语句、函数, 以及 Swift 特有的闭包语法. 下篇将介绍 Swift 的结构体、类与继承以及协议等内容.

1. 基础

Swift 是一门强类型语言,使用关键字 var 定义变量, let 定义常量.

数据类型

常用的数据类型有:

  • 整形 Int , 默认为 Int64.
    • Int16 相当于 short , 2 字节, [-32768, 32767].
    • Int32 相当于 int , 4 字节, [-2147483648, 2147483647].
    • Int64 相当于 long , 8 字节, [-9223372036854775808, 9223372036854775807].
  • 字符串 Sting, 使用双引号定义字面量.
    • 使用三双引号可以换行, 但使用三引号时需另起一行.

    • 使用 \(variable) 语法定义模版字符串

      1
      2
      3
      4
      5
      6
      7
      let to = "world"
      var greeting = "Hello, \(to)!"
      var gretting2 =
      """
      Hello
      \(to)
      """
  • Double, 双精度浮点数
  • Bool, true/false

类型推断

Swift 在定义变量时会进行类型推断. 也可以通过 variable: Type 的语法显示指定类型:

1
2
3
4
let album: String = "Reputation"
let year: Int = 1989
let height: Double = 1.78
let taylorRocks: Bool = true

查看类型

通过函数 type(of: variable) 查看 variable 的类型.

1
type(of: year) == Int.self // true

2. 复杂数据类型

数组 Array

声明数组变量:

1
2
3
var a: [String] = []
var b: Array<String> = []
var c: [Any] = ["ss", 1]

数组访问不能越界, 否则会报错.

Swift 会自动推断数组类型,但当数组中有不同类型元素时要显式声明类型. 例如使用 Any 类型 (Any 是一种特殊的类型,可以表示任何类型):

1
var songs: [Any] = ["Shake it Off", "You Belong with Me", "Back to December", 3]`

只是声明数组变量并不会实际创建它:

1
2
3
4
var songs: [String]

songs[0] = "Shake it off" 
// error: variable 'songs' passed by reference before being initialized

使用下面两种语法,则会被立即创建:

1
2
var songs: [String] = []
var songs = [String]()

集合 Set

无顺序, 不重复. 但可以被迭代. 可以通过 contains 判断某个元素是否在集合中.

1
2
3
4
5
6
7
8
9
let names = Set(["Hozen", "Libai", "Quyuan", "Hozen"])
// (3 elements) {"Libai", "Quyuan", "Hozen"}

for name in names {
print(name)
}

names.contains("hozen") // false
names.contains("Hozen") // true

元组

  • 顺序固定.
  • 一旦定义, 不可增删.
  • 但可以通过 . 操作符来修改.
1
2
3
4
5
6
var hozen = (name: "hozen", age: 22)
hozen.name = "hozenli"
hozen.1 = 27

hozen
// (name "hozen", age 27)

字典 Dictionary

1
2
var hozen: [String: Any] = ["name": "hozen", "age": 21]
hozen["name"]

访问不存在的键时,返回 nil,也可设置默认值:

1
hozen["gender", default: "unkonw"]

创建空字典时需要显示指定数据类型, 然后再添加条目

1
2
var teams = [String: String]()
teams["Paul"] = "red"

枚举 Enum

和很多语言中的枚举类型类似, 在 Swift 中枚举类型没有特别的含义, 但使用它可以使得编码更加安全高效.

1
2
3
4
5
6
7
enum Result {
case success
case failure
}
var res = Result.failure

res = Result.success

可以使用简略语法:

1
2
3
4
5
6
7
enum Weekday {
case monday, tuesday, wednesday, thursday, friday
}

var day = Weekday.monday
day = .tuesday
day = .friday

可以为枚举类型设置关联属性,以方便的适应一些场景

1
2
3
4
5
enum Result {
case success(code: Int, msg: String)
case failure(code: Int, msg: String)
}
var res = Result.failure(code: 404, msg: "Not Found")

当指定枚举值为 Int 类型时, Swift 会为枚举值0 开始自动设置原始值, 此时可以通过 rawValue 定义一个枚举值.

1
2
3
4
5
6
7
8
enum Planet: Int {
case mercury
case venus
case earth
case mars
}

let earth = Planet(rawValue: 2)

也可以手动设置枚举值的原始值,

1
2
3
4
enum ResultCode: Int {
case success = 200
case notFound = 404
}

补充和小结

不同于数组、集合、字典, Swift 中元组是一种匿名数据类型.

Swift 为 ArrayDictionary 设置了特殊的语法, 而 Set 没有:

1
2
var arr = [Int]()
var dict = [String, Int]()

除了各自字面量定义语法还支持尖括号的定义语法:

1
2
3
var arr = Array<Int>()
var dict = Dictionary<String, Int>()
var sset = Set<String>()

3. 运算符和控制语句

运算符

  • 可重载,
  • 可复合: +=
  • 比较运算符: > < >= <= 两边必须同类型.
  • ==, != 判等会进行隐式类型转换.
  • &&, || 要求两侧都是 Bool 类型.

和 Python 类似, 数组可以通过 + 拼接.

条件语句

1
2
3
4
5
6
7
8
var cdt = true

if cdt {
print("true")
} else {
print("false")
}
// true\n

三元运算符 [cdt] ? [exp] : [exp]

Swift 有和 C 语言类似的三元运算符语法

switch 语句

Swift 的 switch 语句中各个 case 默认是自动结束的, 因此不需要 break, 相反当需要继续执行时需要 fallthrough.

1
2
3
4
5
6
7
8
9
10
11
12
13
var weather = "sunny"

switch weather {
case "rain":
print("Bring an umbrella")
case "snow":
print("Wrap up warm")
case "sunny":
print("Wear sunscreen")
fallthrough
default:
print("Enjoy your day!")
}

switch 要确保覆盖所有场景,因此很多情况下 default 是必须的,但也有例外,比如对于 enum 变量来说,容易穷尽所有 case.

范围运算符 ..< ...

范围运算符会返回 Range<Int>. 例如 1..<4 表示 1, 2, 3, 而 1...4 表示 1, 2, 3, 4.

1
2
3
4
5
6
7
8
9
10
11
let score = 85

switch score {
case 0..<50:
print("You failed badly.")
case 50..<85:
print("You did OK.")
default:
print("You did great!")
}
// You did great!\n

for in 循环

for 循环中的迭代变量为临时变量.

当不使用迭代变量时, 可以用 _ 代替.

1
2
3
for _ in 0..<4 {
print("good")
}

while 和 repeat while

1
2
3
4
5
6
7
8
9
var number = 18
while number <= 20 {
print(number)
number += 1
}

repeat {
print(number) // 21\n
} while number <= 20

使用 break 结束当前循环, 使用 continue 跳过当前迭代.

为循环语句添加标签可以使用 break label 跳出指定循环.

5. 函数

函数基本语法

1
2
3
4
5
func square(number: Int) -> Int{
number * number
}

let res: Int = square(number: 8)

需要指明形参名参数类型. 当函数有返回值时要指明返回类型.

使用 return 语句返回, 当函数体只有一行时, 可以省略 return.

和 Python 类似, 可以使用元组返回多个值.

参数标签

除了形式参数名, Swift 还为还为参数添加了标签. 标签用于外部(调用时), 形参名用于内部, 以实现更好的语义化.

1
2
3
4
5
func sayHello(to name: String) {
print("hello! \(name)")
}

sayHello(to: "Allen")

调用函数时, 需要指明参数标签. 没有定义标签时使用形参名作为标签. 也可以把参数标签指定为 _ 来省略调用时的参数标签:

1
2
3
4
5
func sayHello(_ name: String) {
print("hello! \(name)")
}

sayHello("Allen")

默认参数值

与 C 语言类似的语法指定默认参数值

1
2
3
4
5
6
7
8
9
10
func greet(_ name: String, nicely: Bool = true) {
if nicely {
print("hello, \(name)")
} else {
print ("oh no, it's \(name) again...")
}
}

greet("Hozen")
greet("Hozen", nicely: false)

可变参数

在参数类型后添加 ... 使函数可以接受可变数量的参数, 函数体内该参数会被转换为数组. 注意调用时可变参数的写法.

1
2
3
4
5
6
7
func square(numbers: Int...) {
for number in numbers {
print ("\(number) squared is \(number * number)")
}
}

square(numbers: 1,2,3)

可能抛出异常的函数

可能抛出异常的函数要在返回类型前添加关键字 throws. 并在需要抛出异常的地方使用 throw 语句抛出异常.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
enum PasswordError: Error {
case short, obvious
}

func checkPassword(_ password: String) throws -> String {
if password.count < 5 {
throw PasswordError.short
}

if password == "12345" {
throw PasswordError.obvious
}

if password.count < 8 {
return "OK"
} else if password.count < 10 {
return "Good"
} else {
return "Excellent"
}
}

当调用可能会抛出异常的函数时要使用 do try catch 语句.

1
2
3
4
5
6
7
8
9
10
11
12
let string = "12345"

do {
let result = try checkPassword(string)
print("Password rating: \(result)")
} catch PasswordError.short {
print("Please use a longer password.")
} catch PasswordError.obvious {
print("I have the same combination on my luggage!")
} catch {
print("There was an error.")
}

inout 参数

在 Swift 中, 参数默认都是 let 常量不可被修改. 下面的函数会报错, 因为期望修改 number 的值.

1
2
3
4
func doubleInPlace(_ number: Int) {
number *= number
// Left side of mutating operator isn't mutable: 'number' is a 'let' constant
}

如果想要修改参数的值, 需要在定义函数的参数类型前加上关键字 inout. 并且在调用时要在实参变量前加上 &.

1
2
3
4
5
6
7
func doubleInPlace(_ number: inout Int) {
number *= number
}

var num = 5
doubleInPlace(&num)
print(num)

这么设计的目的其实是要求函数不会对传入的值造成副作用, 除非明确的指明. inout 参数的使用类似于 C 语言的指针.

6. 闭包

函数作为“一等公民”可以被赋值给变量, 并作为参数传递, 称之为闭包. (这与 JavaScript 中闭包的概念并不完全相同)

闭包定义和调

1
2
3
4
5
let driving = {
print("I am driving in my car")
}

driving()

对于闭包函数, 定义参数时要使用 in 关键字, 且不可定义参数标签, 调用时也无法使用标签.

1
2
3
4
let driving = {(place: String) in
print("I am going to \(place) in my car")
}
driving("ShangHai")

闭包的返回和函数类似, 只有一行时可以省略 return.

1
2
3
4
5
6
let drivingWithReturn = { (place: String) -> String in
"I am going to \(place) in my car"
}

let message = drivingWithReturn("shanghai")
print(message)

闭包作为参数传递

闭包可以作为参数传递. 使用 func 定义的函数也可以.

闭包和函数作为参数传递时, 需要使用形如 (arg1: Int) -> Void 的语法为参数声明类型.

1
2
3
4
5
6
func travel(action: () -> Void) {
print("I'm getting ready to go")
action()
print("I arrived")
}
travel(action: driving)

尾随闭包语法

这是 Swift 中很独特且又常用的语法形式.

当函数作为最后一个参数时, 可以使用以下语法调用. 即直接使用大括号将要传入的函数以闭包的形式定义.

1
2
3
4
5
6
7
8
9
func travel(action: () -> Void) {
print("I'm getting ready to go")
action()
print("I arrived")
}

travel() {
print("trailing closure syntax")
}

更特别地, 当没有其他参数时可以省略小括号:

1
2
3
travel {
print("trailing closure syntax")
}

常见的闭包语法

  • 带参数的闭包作为参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    func travel(action: (String) -> Void) {
    print("I'm getting ready to go")
    // 把 London 传入闭包 actiong
    action("London")
    print("I arrived")
    }

    travel() {(place: String) in
    print("trailing closure syntax with param \(place)")
    }

  • 有返回值的闭包作为参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    func travel(action: (String) -> String) {
    print("I'm getting ready to go")
    let message = action("London")
    print(message)
    print("I arrived")
    }

    travel() {(place: String) in
    "trailing closure syntax with param \(place)"
    }

  • 一系列简写语法

    • 当闭包作为参数时, 它的参数被明确, 则定义闭包时可以省略类型

    • 类似 func, 函数体只有一行时可以省略 return.

      1
      2
      3
      travel() {place in
      "trailing closure syntax with param \(place)"
      }

    • 参数在声明时类型被明确, 定义时甚至可以直接省略形参, 使用 $[num] 代表.

      1
      2
      3
      4
      5
      6
      7
      8
      func travel(action: (String, Int) -> String) {
      print("I'm getting ready to go.")
      print(action("London", 60))
      print("I arrived")
      }
      travel {
      "I'm goning to \($0) at \($1) miles hour"
      }

返回闭包

1
2
3
4
5
6
7
8
func travel() -> (String) -> Void {
return {(place) in
print("I 'm going to \(place)")
}
}

travel()("London")
// I 'm going to London

闭包作用域保存

闭包会保存作用域链, 这一点和 JavaScript 类似.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func travel() -> (String) -> Void {
var counter = 0
return {(place: String) in
counter += 1
print("I am going to \(place). (calling \(counter) times)")
}
}

let action = travel()

action("Shanghai")
// I am going to Shanghai. (calling 1 times)

action("Jinan")
// I am going to Jinan. (calling 2 times)