学习 Swift 过程中的简要记录,示例意在举明原理。

类型

常量(constant)和变量(variable)是有数据类型,类型描述数据的本质,并为编译器提供数据处理方式的信息。

  • 类型推断(type inference)
    • 常量:let testLet = 42
    • 变量:var testVar = 42
  • 类型注解
    • var testVar: Int
    • var a, b, c: Int(并行注解)

注:可以同时在一行中声明多个变量或常量:let a = 1, b = 2;另外对于必须变化的实例用变量,其它都用常量。

字符串

字符串(Strings)是有序“集合”。

  • 单行: " "
  • 多行: """ """

比较字符串

  • 前缀:hasPrefix(_:)
  • 后缀:hasSuffix(_:)

字符串插值

字符串插值(string interpolation)可以直接在字符串中插入变量或常量,使用 \() 表示占位符。

注释

  • 单行注释: // 单行注释
  • 多行注释:/* 多行注释 */

注:注释可互相嵌套;

数分为整数(Integer)和浮点数(Floating-Point Numbers)。

  • Int:整数,没有小数部分的数字
    • Int:有符号整数(正、负、零)
    • UInt:无符号整数(正,零)
  • Float:浮点数,有小数点的数字
    • Float: 32 位浮点数,精确到小数点后 06 位
    • Double:64 位浮点数,精确到小数点后 15 位(默认)

可使用 Int.minInt.max 查看整数的最大值和最小值。

运算符

一元运算符

  • 减号:-(即负数)
  • 加号:+(一般用于排版)
  • 叹号:!(逻辑非)

二元运算符

算术运算符

  • 加:+,减:-
  • 乘:*,除:/
  • 取余:%

注:加法可用于字符串的拼接,减号可为一元运算符

赋值运算符

  • 相加后赋值:+=,相减后赋值:-=
  • 相乘后赋值:*=,相除后赋值:/=
  • 左移后赋值:<<=,右移后赋值:>>=
  • 按位与后赋值:&=,按位或后赋值:!=,按位异或后赋值:^=
  • 求余后赋值:%=

注:

  • 可以使用元组同时给多个元素赋值;
  • 赋值操作不产生返回值;

比较运算符

  • 小于:<,大于:>
  • 相等:==,不等:!=
  • 小于等于:<=,大于等于:>=
  • 相等于:===,不相等于:!===;(指向同一个引用)

逻辑运算符

  • 逻辑与 AND:&&(一假,为假;二真,为真)
  • 逻辑或 OR :|| (一真,为真;二假,为假)
  • 逻辑非 NOT:! (真为假,假为真)

注:逻辑非为一元运算符

区间运算符

  • 闭合区间运算符
    • x...y:包含 y
  • 半开区间运算符
    • x..<y:不包含 y
    • x..<arrays.count:遍历列表
  • 单侧区间运算符
    • arrays[..<y]:从 0 到 y
    • arrays[x...]:从 x 到 结尾

溢出操作符

  • &+
  • &-
  • &*

空合运算符

合并空值运算符 a ?? b,如果 a 有值则展开,否则返回 b。

// 三元运算符
a != nil ? a! : b

// 合并空值运算符
a ?? b

三元运算符

类似 if / else,但语法上更加简洁:a ? b : c(如果 a 为真,则执行 b;否则执行 c)。

// if / else
if a {
   b
} else {
    c
}

控制流

if

if 语句为真才执行。

if conditon {
    // 执行一些操作
}

if / else

if / else 语句要么执行第一个分支,要么执行另一个分支。

if conditon {
    // 执行一些操作
} else {
   // 执行一些操作
}

if / else if / else

if / else if / else 语句根据某个特定的逻辑条件执行代码(else 可省略)。

if conditon {
    // 执行一些操作
} else if conditon {
   // 执行一些操作
} else {
    // 否则执行
}

可选绑定

if let testIf = Int(str) {
    // 执行一些操作
} else {
    // 执行一些操作
}

switch

if / else 和 swift 区别在于前者关注条件是否为真执行代码,而后者关注某个值是与其中一个分支是否匹配。

switch aValue {
case value1:
    // 执行一些操作
case value2:
   // 执行一些操作
default:
    // 无匹配时执行
}

注:

  • 每个分支的类型都必须和被比较的类型一样;
  • 必须存在 default 语句(if case 除外);

复合分支

复合分支中可以有多个值,使用逗号分隔。

switch aValue {
case value 1, 2, 3:
    // 执行一些操作
default:
    // 无匹配时执行
}

区间匹配

如果要匹配的值是连续的,则可以使用区间匹配(range matching)。

switch aValue {
case value1...value3:
    // 执行一些操作
default:
    // 无匹配时执行
}

元组匹配

switch aValue {
case (value1, value2):
    // 执行一些操作
case (value1, _):
    // 可以忽略另一个值
default:
    // 无匹配时执行
}

值绑定

值绑定(value binding)能在某个特定分支中把待匹配的值绑定到本地的常量或变量上(这个常量只能在该分支中使用)。

switch aValue {
case value1:
    // 执行一些操作
case value2:
    // 执行一些操作
case let unvalue:
    // 无匹配时执行
}

其实标准的 default 分支也能得到同样的结果:

switch aValue {
case value1:
    // 执行一些操作
case value2:
    // 执行一些操作
default:
    let unvalue = aValue
}

where 子句

where 能让你额外检查一些条件,只有满足这些条件后才会匹配这个分支。

switch aValue {
case value1...value3 where aValue == x:
    // 只有当 aValue 在匹配区间并等于 x 时才会执行
default:
    // 无匹配时执行
}

if case

if case 在只有一个分支且不想要 default 分支时,能够比 switch 更优雅的实现。

// switch 区间匹配
switch aValue {
case value1...value3:
    // 执行一些操作
default:
    // 不需要 default 时
}

// if case 实现
if case value1...value3 = aValue {
    // 执行一些操作
}

for-in 循环

for 适合对已知重复次数或容易推断的任务。

循环指定次数

for i in 0...9 {
    print(i)
}

遍历数组

let x = ["a", "b", "c"]

for i in x {
    print(i)
}

where 子句

// 打印偶数的常规实现
for i in 1...10 {
  if i % 2 {
    print(i)
  }
}

// 使用 where 子句实现
for i in 1...10 where i % 2 == 0 {
    print(i) // 结果包含 10
}

指定间隔

// 间隔一位,即打印偶数
for i in stride(from: 0, to: 10, by: 2) {
    print(i) // 结果不包含 0
}

for i in stride(from:0, through: 10, by 2) {
    print(i) // 结果包含 0
}

while 循环

while 循环只要条件为真,就会一直循环下去,适合在满足某些条件时重复执行的任务。

var i = 0

// 打印 1 至 9
while i < 9 {
    i += 1
    print(i)
}

repeat-while 循环

repeat-while 循环会在开始之前执行至少执行一次,然后才计算条件。

var i = 0
repeat {
    i += 1
    print(i)
} while i > 9

控制转移语句

控制状态语句(control transfer statement)能让你修改某个控制流中的代码执行顺序。

fallthrough

fallthrough 语句能在不需要进行匹配的情况下就能执行某个分支中的代码。

switch aValue {
case value1:
    // 执行一些操作
    fallthrough
case value2: // 这个分支百分百会执行
    // 执行一些操作
default:
    // 无匹配时执行
}

注:另外还有 breakcontinue

带标签语句

label name: while condition {
    // 执行一些操作
}

可空类型

可空类型(optional)用来指定某个实例可能没有值的情况,即一个实例的值可能为 nil。

// 使用 ? 声明可空类型
var testOptional: String?

强制展开

强制展开(forced unwrapping)会访问可空实例封转的值,无论是否有值。

var testOptional: String?
testOptional = "42"

if testOptional != nil {
    print(testOptional!)// 使用 ! 强制展开
}

注:

直接打印可空类型将会出现 Optional( )

可空实例绑定

可空实例绑定(optional binding)是一种固定模式,主要用于判断可空实例是否有值。

var testOptional: String?
testOptional = "42"

if let bindOptional = testOptional {
    print(bindOptional)
}

可空实例检查

var testOptional: String?
testOptional = "42"

if let bindOptional = testOptional, Int(bindOptional) == 42 {
    print(bindOptional) // 只有当前两个条件都满足时才会执行
}

隐式展开可空类型

隐式展开可空类型(implicitly unwrapped optional)和普通可空类型的区别在于:它不需要展开。

var testOptional: String! // 使用 ! 表示隐式展开可空类型

可空链式调用

可空链式调用(optional chaining)提供了一种对可空实例进行查询以判断其是否包含值的机制。

原地修改可空实例

testOptional?.append(" TEST")

nil 合并运算符

nil 合并运算符(nil coalescing operator))用于可空实例中获取值,或者返回一个默认值。

let z = x ?? "value" // 如果无值则赋值 value

注:即运算符中的空合运算符。

容器

容器类型 有序 唯一 存储
数组 元素
字典 键值对
集合 元素 元素

元组

元组(tuple)是开发者认为具有逻辑关联的两个或多个值的有限组合,它的值可以是任何类型

创建

// 创建
let http404Error = (404, "Not Found")

// 元组元素命名
let http404Error = (statusCode: 404, statusMessage: "Not Found") 

赋值

let http404Error = (404, "Not Found")

// 利用元组分别赋值
let (statusCode, statusMessage) = http404Error

// 可以忽略值
let (statusCode, _) = http404Error

访问

let http404Error = (statusCode: 404, statusMessage: "Not Found") 

// 通过下标访问
http404Error.0

// 通过元素名称访问
http404Error.statusCode

数组

数组(array)是值的有序集合,数组的每个位置都用索引标记,任何值都可以在数组中出现多次,通常用于对于值的顺序很重要的场合。

创建

  • 未初始化
    • var testArrays: Arrays<Int> // 未初始化
    • var testArrays: [Int] // 可省略 Arrays
  • 空数组
    • var testArrays = [Int]()
    • var testArrays = Array<Int>()
    • var testArrays: [Int] = []
    • var testArrays: Array<Int> = []
  • 赋值
    • var testArrays = [1, 2, 3] 
    • var testArrays: [Any] = [1, 2, “Three"] 
    • var testArrays: Array<Int> = [1, 2 ,3] 
    • var testArrays: Array(repeating: 0, count: 8):创建长度为 8,值为 0 的数组

基本操作

以数组:var testArrays = [1, 2, 3]为例:

  • 添加
    • 指定位置:testArrays.insert(42, at: 0) // 返回 1, 2, 3, 42
    • 最后位置:testArrays.append(42)testArrays += [42] // 同上
  • 访问
    • 指定元素:testArrays[1] // 从 0 计数,所以会返回 2
    • 指定范围:testArrays[1...2] // 返回 2, 3
    • 第一元素:testArrays.firsttestArrays[0] //返回 0
    • 最后元素:testArrays.lasttestArrays[testArrays.count-1] // 返回 3
  • 改变
    • 指定位置:testArrays[0] = 42 // 返回 42, 2, 3
    • 指定范围:testArrays[0...1] = [42] // 返回 42, 3
  • 删除
    • 指定位置:testArrays.remove(at: 1) // 返回 0, 3
  • 检查
    • 长度:testArrays.count
    • 是否为空:if testArrays.isEmpty { print("为空") }
    • 指定元素索引:testArrays.firstIndex(of: x)
    • 指定元素是否存在:testArrays.contains(x)
  • 遍历
    • 值:for i in testArrays { print(i) }
    • 索引:for i in 0..<testArrays.count { print(i) }
    • 索引和值:for (i, v) in testArrays.enumerated() { print(i, v)}

注:两个数组即使内容一样,如果顺序不同,也会被判定为不同。

集合

集合是一组互不相同的实例的无序组合。

创建

  • 空集合
    • var testSets = Set<Int>()
    • var testSets: Set<Int> = []
  • 赋值
    • var testSets: Set<Int> = [1, 2, 3] //
    • var testSets: Set = [1, 2, 3] // 可省略类型

基本操作

以集合:var testSets: Set = [1, 2, 3]为例:

  • 添加
    • testSets.insert(42)
  • 删除
    • testSets.remove(42)
  • 检查
    • 长度:testSets.count
    • 是否为空:if testSets.isEmpty { print("为空") }
    • 包含元素:testSets.contains(42)
  • 遍历
    • 默认:for i in testSets { print(i) }
    • 按顺序:for i in testSets.sorted() { print(i) }
  • 集合操作
    • 并集:testSetsA.union(testSetsB) // 去重合并
    • 交集:testSetsA.intersection(testSetsB) // 返回相同部分
    • 补集:testSetsA.subtracting(testSetsB) // 保留 A 与 B 不同处
    • 异或:testSetsA.symmetricDifference(testSetsB) // 去处交集
  • 集合判断
    • 子集:testSetsB.isSubset(of: testSetsA) // B 是 A 的子集,即 A 包含 B 的所有元素
    • 超集:testSetsA.isSuperset(of: testSetsB) // A 是 B 的超集
    • 不相交:testSetsB.isDisjoint(with: testSetsC) // B 和 C 没有交集,即没有重复元素
    • 严格子集:testSetsB.isStrictSubset(of: testSetsA) // B 是 A 的子集,且不相等
    • 严格超集:testSetsA.isStrictSubset(of: testSetsB) // A 是 B 的超集,且不相等

字典

字典(dictionary)使用键值对(key-value pair)组织其内容的容器类型,字典中的键必须是唯一的。

创建

  • 空字典
    • var testDicts = [String: Int]()
    • var testDicts = Dictionary<String, Int>()
    • var testDicts: [String: Int] =[:] 
    • var testDicts: Dictionary<String, Int> = [:] 
  • 赋值
    • var testDicts = ["num": 42] // 如果值要任意类型,必须指定
    • var testDicts:[String: Any] = ["num": 42] // 任意类型
    • var testDicts:Dictionary<String, Int> = ["num": 42] // 指定类型

注:

  • ():使用字典类型的默认初始化方法;
  • [:]:使用字面量语法创建空字典;

基本操作

  • 访问
    • testDicts
  • 添加
    • testDicts["key"] = value// 如存在便为修改
    • testDicts.updateValue(value, forKey: "key") // 指定键
  • 删除
    • testDicts["num"] = nil
    • testDicts.removeValue(forKey: "key") // 会返回删除的值
    • testDicts.removeAll() // 清空字典
  • 检查
    • 长度:testDicts.count
    • 容量:testDicts.capacity
    • 增加容量:testDicts.capacity(10)
    • 是否为空:if testDicts.isEmpty { print("为空") }
  • 遍历
    • 键:for key in testDicts.keys { print(key) }
    • 值:for value in testDicts.values {print(value)}
    • 键值:for (key, value) in testDicts { print(key, value) }
  • 转换
    • 转键为集合:let dictsToSet = Set(testDicts.keys)
    • 转键为数组:let dictsToArray = Array(testDicts.keys)
    • 转值为集合:let dictsToSet = Set(testDicts.values)
    • 转值为数组:let dictsToArrays = Array(testDicts.values)

函数

函数(Functions)是一组有名字的独立代码,用来执行特定的任务。

函数的三种类型:

  • 全域函数(global functions):即一般所说的函数,在每个地方都可以使用;
  • 递归函数(nested functions):函数内部的函数,只能在函数体内使用,或作为返回值;
  • 闭包(closure):即匿名函数,没有名称,可以在函数中被传递和使用;

定义

使用 func 关键字定义函数,一个最简单的函数即没有参数,也没有返回值:

func sayHello() {
    print("Hello, World!")
}

sayHello() // 调用函数

参数

参数(parameter)可以根据调用者给函数传递的数据来改变自己的值,而函数利用参数传递给自己的参数来执行。

func sayHello(name: String) {
    print("Hello, \(name)")
}

sayHello("Swift")

注:

  • 形参(parameter)和实参(argument),类似于变量和值;
  • 参数可以有多个,以逗号分隔;

参数标签

参数标签(parameter label)用于外部调用,而参数名(parameter name)用于函数内部,默认情况下以参数名作为参数标签。

func sayHello(to name: String) {
    print("Hello, \(name)")
}

sayHello(to: "Siwft")

注:

  • 参数标签在参数名之前,用空格隔开;
  • 如有参数标签,调用时需明确指明;
  • 参数标签可使用下划线 _ 省略;

参数默认值

如果参数默认值(Default parameter values)存在,调用函数时便可省略实参。

func sayHello(name: String = "Swift") {
    print("Hello, \(name)!")
}

sayHello() // 返回:Hello, Swift!

注:参数默认应该放在参数末尾;

可变参数

可变参数(Variadic parameters)接受零个或以上输入值作为实参。

func sayHello(name: String...) {
    for n in name {
        print("Hello, \(n)!")
    }
}

注:

  • 一个函数只能有一个可变参数;
  • 一般放在参数列表中的最后一个;

传入传出参数

传入传出参数(In-Out Parameters)可以让函数影响函数体以外的变量。

func sayHello(name: String) {
    name += "Hello"
}

var name = "Swift"
sayHello(name: &name) // name 的值将变为 SwiftHello

注:

  • in-out 参数不能有默认值;
  • 变长参数不能标记为 inout;

返回值

函数的返回值(return)即在函数执行结束之后返回的一些信息。

func sayHello(name: String) -> String {
    return "Hello, \(name)!"
}

print(sayHello(name: "Swift")) //同样返回 Hello, Swift!

注:

  • 函数的返回值类型必须相同;
  • 可以有多个返回值,如:-> (String, String)
  • 返回值同样可以使用命名元组(named tuple),如:-> (name: [String], age: [Int])

可空返回类型

如果一个函数的返回值有可能为空的话,可以使用可空返回值(optional return types)。

func sayHello() -> (String, Int)? {
    return nil
}

// 也可以选择返回单个可空返回值
func sayHello() -> (String?, Int) {
     return (nil, 42)
}

函数类型

函数类型(function type)由函数参数和返回值组成。

func sayHello(name: String) -> String {
    return "Hello, \(name)")
}

这个函数的返回类型为:(Int) -> Int,意思是这个函数接受一个 String 的值,然后也返回一个 String 值。如果一个函数即无参数,也无返回值,则标注为:() -> Void

使用函数类型

var sayHello: (String) -> String = sayHello

// 或者像变量一样自动推导
var sayHello = sayHello

// 像调用变量一样使用函数
print("\(sayHello("Wu"))")

上面的操作可以把函数 sayHello 关联到变量 sayHello,然后可以直接使用这个变量调用函数的功能。

嵌套函数

嵌套函数(nested functions)在一个函数的内部声明并实现,只能在函数内部使用,或者作为返回值。

func sayHello(name: String) -> String {
    func sayBye() -> String {
        return "\("W"), See you again."
    }
return sayBye()
}

注:任何在函数作用域内部的东西都对函数可见;

提前退出函数

guard 语句可以判断当条件不符合时,阻止程序继续执行。

func sayHello(name: String) {
    //当 name 的值不是 Wu 时,才执行
    guard name != "Wu" else { 
        print("Bye \(name)")
        return // 不可省略
    }
    print("Hello, \(name)")
}

sayHello(name: "Wu") // 返回 Bye Wu

Void

Void 是 () 的类型别名,当函数没有显式返回值时,作为默认返回值。

func sayHello() {
    print("Hello, \(name)!")
}

// 默认返回值
func sayHello() -> Void {
    print("Hello, \(name)!")
}

// 空括号 ()标准库对 Void 的映射
func sayHello() -> () {
    print("Hello, \(name)!")
}

注:前面提到的无参数无返回值函数类型 () -> Void,亦可标注为 () -> ()

闭包

闭包(closure)可以理解为没有名字的函数。

// 闭包表达式语法
{ (parameters) -> type in
    statements
}

let nums = [16, 4, 64, 8, 32]

// 函数实现
func sortClosure(i: Int, j: Int) -> Bool {
    return i < j
}
// 将返回按从大到小排序好的数组
let sortNums = nums.sorted(by: sortClosure)

// ---------------------------------------------------------------------

// 闭包实现
let sortNums = nums.sorted(by: {(i: Int, j: Int) -> Bool in return i < j})

// 利用类型推断
let sortNums = nums.sorted(by: {i, j in return i < j})

// 省略 return 关键字,单一闭包隐式返回,如果有多个表达式,必须显式 return
let sortNums = nums.sorted(by: {i, j in i < j})

// 参数名称简写,多个参数可以使用 $1 $2 $3
let sortNums = nums.sorted(by: {$0 < $1})

// 使用尾部闭包语法
let sortNums = nums.sorted{$0 < $1}

注:

  • 如果要把闭包作为最后一个参数传递给函数,则可以使用尾部闭包语法(trailing closure syntax)
  • sorted(by:) 是一个接受两个整数进行比较并返回布尔值表示哪个整数在前的闭包;(每一个数向前比较,直到返回 false 为止)

闭包值捕获

闭包可以捕获(capture)它所定义的上下文环境中的常量和变量,并可以在闭包体内使用和修改这些常量和变量的值,即使这些常量或变量的作用域已经不存在。

func testFunc(num: Int) -> () -> Int {
    var total = 0
    func totalClosure() -> Int {
        total += num
        return total
    }
    return totalClosure
}

let test = testFunc(num: 42)
test() // 返回 42,每次引用将增加 42
test() // 返回 84

信息

版本

  • Xcode 10.1
  • Swift 4.2.1

参考

更新

  • Update:2019-03-24
    • 第一次更新