https://docs.swift.org/swift-book/documentation/the-swift-programming-language
这是笔者上手 Swift 时做的笔记。
简单值
常用的基本类型:Int、Double、Bool、String
集合类型:Array、Set、Dict
使用 var 创建变量,使用 let 创建常量。其作用相当于 Rust 中的 let mut 和 let。
var myVariable = 42
myVariable = 50
let myConstant = 42let explicitDouble: Double = 70 // 类型注解
var optionalString: String? = "Hello" // 可选的空值
Swift 永远不会进行自动类型转换。
可以通过 \() 来将值填充到字符串:
let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I have \(apples + oranges) pieces of fruit."
类似 Python,Swift 支持使用三个双引号定义多行字符串。每行的首部缩进会根据结束的三个双引号的缩进进行消除。最后一项可以带上逗号。
通过 [] 来创建数组和字典。
var fruits = ["strawberries", "limes", "tangerines"]
fruits[1] = "grapes"
fruits.append("blueberries")
let emptyArray: [String] = [] // 空数组,赋值时必须制定类型var occupations = ["Malcolm": "Captain","Kaylee": "Mechanic",]
occupations["Jayne"] = "Public Relations"
let emptyDictionary: [String: Float] = [:]
Swift 中的空值是 nil。
整数
默认的整数类型是 Int。在 32 位平台上,Int 等价于 Int32;在 64 位平台上,Int 等价于 Int64。
无符号整型是在对应的 Int 类型前面加 U。仅在需要与平台原生字长相同大小的无符号整数类型时,才应该使用 UInt。其他情况(包括明确为非负整数时)应当使用 Int。
浮点
包括 Float16、Float80、Double 等类型。如果对精度没有明确要求,应当一律使用 Double。
注释
Swift 中的注释与 C/C++ 基本一致,即 // 单行注释和 /**/ 多行注释。但 Swift 中的多行注释可以嵌套。
分号
Swift 中一行语句结束的分号是可选的,根据官方指南来看,倾向于不写分号。但如果一行中写了两个语句,则必须写分号来分隔语句。
控制流
if
let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {if score > 50 { // 条件表达式的括号是可选的。teamScore += 3} else {teamScore += 1}
}
类似 Rust,可以对 if 语句求值。
let scoreDecoration = if teamScore > 10 {"🎉"
} else {""
}
if let 能够匹配可能为空的值:
if let name = optionalName {greeting = "Hello, \(name)"
} // optionalName 为 nil 时,大括号将被跳过
可用 ?? 指定默认值。
let nickname: String? = nil
let fullName: String = "John Appleseed"
let informalGreeting = "Hi \(nickname ?? fullName)"
switch - case
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"): // switch 并不局限于整数和相等性测试print("Is it a spicy \(x)?")
default: // 必须,因为 switch 必须是可穷尽(exhaustive)的print("Everything tastes good in soup.")
}
case 字句是自动退出的,因此不需要 break。
for
通过 for in 来遍历一个可迭代的对象。字典是无序集合,不能假定遍历的顺序。
let interestingNumbers = ["Prime": [2, 3, 5, 7, 11, 13],"Fibonacci": [1, 1, 2, 3, 5, 8],"Square": [1, 4, 9, 16, 25],
]
var largest = 0
for (_, numbers) in interestingNumbers {for number in numbers {if number > largest {largest = number}}
}
while
repeat while 相当于 C/C++ 中的 do while。
var n = 2
while n < 100 {n *= 2
}var m = 2
repeat {m *= 2
} while m < 100
函数和闭包
func greet(person: String, day: String) -> String {return "Hello \(person), today is \(day)."
}
greet(person: "Bob", day: "Tuesday")
在 Swift 中调用函数的参数名和函数内部的参数名可以不一致:
func greet(_ person: String, on day: String) -> String {return "Hello \(person), today is \(day)."
}
greet("John", on: "Wednesday")
函数可以返回元组,从而返回多个值。可以通过名称或索引来获取元组中的值。
func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) {// 略return (min, max, sum)
}
let statistics = calculateStatistics(scores: [5, 3, 100, 3, 9])
print(statistics.sum)
print(statistics.2)
函数可以嵌套。内部函数可以访问外部函数的变量。
func returnFifteen() -> Int {var y = 10func add() {y += 5}add()return y
}
函数是一等公民,因此函数可以充当返回值,也可以充当参数。
func makeIncrementer() -> ((Int) -> Int) {func addOne(number: Int) -> Int {return 1 + number}return addOne
}func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {for item in list {if condition(item) {return true}}return false
}
函数实际上是闭包的特例。闭包可以通过一组花括号创建,使用 in 将参数和代码隔开。
numbers.map({ (number: Int) -> Int inlet result = 3 * numberreturn result
})
闭包的参数类型注解和返回值类型注解都可以省略。可以通过编号来引用参数。当闭包是函数的唯一参数时,可以省略函数的调用括号。
let mappedNumbers = numbers.map({ number in 3 * number })
let sortedNumbers = numbers.sorted { $0 > $1 }
对象和类
在类的上下文中声明的变量自动成为类的属性。类的初始化方法(构造函数)是 init。类的析构函数是 deinit。
class NamedShape {var numberOfSides: Int = 0var name: Stringinit(name: String) {self.name = name}func simpleDescription() -> String {return "A shape with \(numberOfSides) sides."}
}
在定义属性时可以指定 getter 和 setter。
// 假设这是在一个类的上下文中
var perimeter: Double {get {return 3.0 * sideLength}set {sideLength = newValue / 3.0}
}
子类通过冒号来继承超类。子类对超类方法的覆写必须显式使用 override 关键字,否则将被报错。
override func simpleDescription() -> String {return "A square with sides of length \(sideLength)."
}
枚举和结构体
枚举也可以有成员函数。枚举的属性默认从 0 开始计数,但也可以手动指定。
enum Rank: Int {case ace = 1case two, three, four, five, six, seven, eight, nine, tencase jack, queen, kingfunc simpleDescription() -> String {switch self {case .ace:return "ace"case .jack:return "jack"case .queen:return "queen"case .king:return "king"default:return String(self.rawValue)}}
}
结构体在大部分方面都与类行为一致。二者最显著的区别是结构体对象在传递时是传值,而类对象是传引用。
struct Card {var rank: Rankvar suit: Suitfunc simpleDescription() -> String {return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"}
}
异步和并发
在函数的参数列表后面、返回值类型注解的箭头前面加上 async 关键字可将此函数标记为异步函数。
func fetchUserID(from server: String) async -> Int {if server == "primary" {return 97}return 501
}
在函数调用前面使用 await 使此调用成为异步调用。
使用 Task 可以在同步代码中调用异步函数。
Task {await connectUser(to: "primary")
}
actor 类似于类,区别是不同异步函数可以安全地同时与同一 actor 实例交互。
协议与扩展
使用 protocol 关键字声明一个协议。类、枚举和结构体都可以“继承”协议。
错误处理
任何遵循 Error 协议的类都可以表示错误。
使用 throw 抛出错误,使用 throws 标记一个可能抛出错误的函数。
func send(job: Int, toPrinter printerName: String) throws -> String {if printerName == "Never Has Toner" {throw PrinterError.noToner}return "Job sent"
}
有两种常见的错误处理方法。
使用 do - catch
类似 try - catch,但此处的 try 放在可能出现错误的代码之前。在 catch 块中,若非明确指定名称,错误会自动获得名为 error 的名称。
do {let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")print(printerResponse)
} catch {print(error)
}
可以使用多个 catch 来捕获不同的错误。
do {let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")print(printerResponse)
} catch PrinterError.onFire {print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {print("Printer error: \(printerError).")
} catch {print(error)
}
使用 try?
可以通过 try? 将结果转换为可选值。如果函数发生错误,则会丢弃具体的错误,结果为 nil;否则,结果是包含函数返回值的可选值。
let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")
defer {} 代码块用于编写在函数执行过程中无论是否出错都会最终执行的代码。
泛型
使用尖括号加上名称来定义一个泛型。
func makeArray<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] {var result: [Item] = []for _ in 0..<numberOfTimes {result.append(item)}return result
}
makeArray(repeating: "knock", numberOfTimes: 4)
在函数体之前使用 where 可以指定泛型需要满足的要求。
func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Boolwhere T.Element: Equatable, T.Element == U.Element
{for lhsItem in lhs {for rhsItem in rhs {if lhsItem == rhsItem {return true}}}return false
}
anyCommonElements([1, 2, 3], [3])