Swift 语法

YVTU

语法基础

变量

变量是可变的,使用 var 修饰,常量是不可变的,使用 let 修饰。类、结构体和枚举里的变量是属性。

1
2
3
4
5
6
7
8
9
10
var v1:String = "hi" // 标注类型
var v2 = "类型推导"
let l1 = "标题" // 常量

class a {
let p1 = 3
var p2: Int {
p1 * 3
}
}

属性没有 set 可以省略 get,如果有 set 需加 get。变量设置前通过 willSet 访问到,变量设置后通过 didSet 访问。

打印

控制台打印值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
print("hi")
let i = 14
print(i)
print("9月\(i)是小柠檬的生日")

for i in 1...3{
print(i)
}
// output:
// 1
// 2
// 3

// 使用terminator使循环打印更整洁
for i in 1...3 {
print("\(i) ", terminator: "")
}
// output:
// 1 2 3

注释

1
2
3
4
5
6
7
8
// 单行注释
/*
多行注释第一行。
多行注释第二行。
*/
// MARK: 会在 minimap 上展示
// TODO: 待做
// FIXME: 待修复

!?可选

可能会是 nil 的变量就是可选变量。当变量为 nil 通过??操作符可以提供一个默认值。

1
2
var o: Int? = nil
let i = o ?? 0

SE-0345 if let shorthand for shadowing an existing optional variable 引入的新语法,用于 unwrapping optinal。

1
2
3
4
5
6
7
8
let s1: String? = "hey"
let s2: String? = "u"
if let s1 {
print(s1)
}

guard let s1, let s2 else { return }
print(s1 + " " + s2)

闭包

闭包也可以叫做 lambda,是匿名函数,对应 OC 的 block。

1
2
3
4
5
6
7
8
9
10
11
12
13
let a1 = [1,3,2].sorted(by: { (l: Int, r: Int) -> Bool in
return l < r
})
// 如果闭包是唯一的参数并在表达式最后可以使用结尾闭包语法,写法简化为
let a2 = [1,3,2].sorted { (l: Int, r: Int) -> Bool in
return l < r
}
// 已知类型可以省略
let a3 = [1,3,2].sorted { l, r in
return l < r
}
// 通过位置来使用闭包的参数,最后简化如下:
let a4 = [1,3,2].sorted { $0 < $1 }

函数也是闭包的一种,函数的参数也可以是闭包。@escaping 表示逃逸闭包,逃逸闭包是可以在函数返回之后继续调用的。@autoclosure 表示自动闭包,可以用来省略花括号。

SE-0326 提高了 Swift 对闭包使用参数和类型推断的能力。如下代码:

1
2
3
4
5
6
7
8
9
let a = [1,2,3]
let r = a.map { i in
if i >= 2 {
return "\(i) 大于等于2"
} else {
return "\(i) 小于2"
}
}
print(r)

函数

函数可以作为另一个函数的参数,也可以作为另一个函数的返回。函数是特殊的闭包,在类、结构体和枚举中是方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 为参数设置默认值
func f1(p: String = "p") -> String {
"p is \(p)"
}

// 函数作为参数
func f2(fn: (String) -> String, p: String) -> String {
return fn(p)
}

print(f2(fn:f1, p: "d")) // p is d

// 函数作为返回值
func f3(p: String) -> (String) -> String {
return f1
}

print(f3(p: "yes")("no")) // p is no

函数可以返回多个值,函数是可以嵌套的,也就是函数里内可以定义函数,函数内定义的函数可以访问自己作用域外函数内的变量。inout 表示的是输入输出参数,函数可以在函数内改变输入输出参数。defer 标识的代码块会在函数返回之前执行。

函数在 Swift 5.4 时开始有了使用多个变量参数的能力,使用方法如下:

1
2
3
4
5
6
7
8
func f4(s: String..., i: Int...) {
print(s)
print(i)
}

f4(s: "one", "two", "three", i: 1, 2, 3)
/// ["one", "two", "three"]
/// [1, 2, 3]

嵌套函数可以重载,嵌套函数可以在声明函数之前调用他。

1
2
3
4
5
6
7
func f5() {
nf5()
func nf5() {
print("this is nested function")
}
}
f5() // this is nested function

访问控制

在 Xcode 里的 target 就是模块,使用 import 可导入模块。模块内包含源文件,每个源文件里可以有多个类、结构体、枚举和函数等多种类型。访问级别可以通过一些关键字描述,分为如下几种:

  • open:在模块外可以调用和继承。
  • public:在模块外可调用不可继承,open 只适用类和类成员。
  • internal:默认级别,模块内可跨源文件调用,模块外不可调用。
  • fileprivate:只能在源文件内访问。
  • private:只能在所在的作用域内访问。

重写继承类的成员,可以设置成员比父类的这个成员更高的访问级别。Setter 的级别可以低于对应的 Getter 的级别,比如设置 Setter 访问级别为 private,可以在属性使用 private(set) 来修饰。

Regex

标准库多了个 Regex 类型,Regex 语法与 Perl、Python、Ruby、Java、NSRegularExpression 和许多其他语言兼容。可以用 let regex = try! Regex(“a[bc]+”) 或 let regex = /a[bc]+/ 写法来使用。SE-0350 Regex Type and Overview 引入 Regex 类型。SE-0351 Regex builder DSL 使用 result builder 来构建正则表达式的 DSL。SE-0354 Regex Literals 简化的正则表达式。SE-0357 Regex-powered string processing algorithms 提案里有基于正则表达式的新字符串处理算法。

RegexBuilder 文档

session Meet Swift RegexSwift Regex: Beyond the basics

Regex 示例代码如下:

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
let s1 = "I am not a good painter"
print(s1.ranges(of: /good/))
do {
let regGood = try Regex("[a-z]ood")
print(s1.replacing(regGood, with: "bad"))
} catch {
print(error)
}
print(s1.trimmingPrefix(/i am /.ignoresCase()))

let reg1 = /(.+?) read (\d+) books./
let reg2 = /(?<name>.+?) read (?<books>\d+) books./
let s2 = "Jack read 3 books."
do {
if let r1 = try reg1.wholeMatch(in: s2) {
print(r1.1)
print(r1.2)
}
if let r2 = try reg2.wholeMatch(in: s2) {
print("name:" + r2.name)
print("books:" + r2.books)
}
} catch {
print(error)
}

使用 regex builders 的官方示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Text to parse:
// CREDIT 03/02/2022 Payroll from employer $200.23
// CREDIT 03/03/2022 Suspect A $2,000,000.00
// DEBIT 03/03/2022 Ted's Pet Rock Sanctuary $2,000,000.00
// DEBIT 03/05/2022 Doug's Dugout Dogs $33.27

import RegexBuilder
let fieldSeparator = /\s{2,}|\t/
let transactionMatcher = Regex {
/CREDIT|DEBIT/
fieldSeparator
One(.date(.numeric, locale: Locale(identifier: "en_US"), timeZone: .gmt)) // 👈🏻 we define which data locale/timezone we want to use
fieldSeparator
OneOrMore {
NegativeLookahead { fieldSeparator } // 👈🏻 we stop as soon as we see one field separator
CharacterClass.any
}
fieldSeparator
One(.localizedCurrency(code: "USD").locale(Locale(identifier: "en_US")))
}

在正则表达式中捕获数据,使用 Capture:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let fieldSeparator = /\s{2,}|\t/
let transactionMatcher = Regex {
Capture { /CREDIT|DEBIT/ } // 👈🏻
fieldSeparator

Capture { One(.date(.numeric, locale: Locale(identifier: "en_US"), timeZone: .gmt)) } // 👈🏻
fieldSeparator

Capture { // 👈🏻
OneOrMore {
NegativeLookahead { fieldSeparator }
CharacterClass.any
}
}
fieldSeparator
Capture { One(.localizedCurrency(code: "USD").locale(Locale(identifier: "en_US"))) } // 👈🏻
}
// transactionMatcher: Regex<(Substring, Substring, Date, Substring, Decimal)>

基础类型

数字

数字的类型有 Int、Float 和 Double

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
// Int
let i1 = 100
let i2 = 22
print(i1 / i2) // 向下取整得 4

// Float
let f1: Float = 100.0
let f2: Float = 22.0
print(f1 / f2) // 4.5454545

let f3: Float16 = 5.0 // macOS 还不能用
let f4: Float32 = 5.0
let f5: Float64 = 5.0
let f6: Float80 = 5.0
print(f4, f5, f6) // 5.0 5.0 5.0

// Double
let d1: Double = 100.0
let d2: Double = 22.0
print(d1 / d2) // 4.545454545454546

// 字面量
print(Int(0b10101)) // 0b 开头是二进制
print(Int(0x00afff)) // 0x 开头是十六进制
print(2.5e4) // 2.5x10^4 十进制用 e
print(0xAp2) // 10*2^2 十六进制用 p
print(2_000_000) // 2000000

// isMultiple(of:) 方法检查一个数字是否是另一个数字的倍数
let i3 = 36
print(i3.isMultiple(of: 9)) // true

处理数字有 floor、ceil、round。floor 是向下取整,只取整数部分;cell 是向上取整,只要有不为零的小数,整数就加1;round 是四舍五入。

布尔数

布尔数有 true 和 false 两种值,还有一个能够切换这两个值的 toggle 方法。

1
2
3
var b = false
b.toggle() // true
b.toggle() // false

字符串

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
let s1 = "Hi! This is a string. Cool?"

/// 转义符 \n 表示换行。
/// 其它转义字符有 \0 空字符)、\t 水平制表符 、\n 换行符、\r 回车符
let s2 = "Hi!\nThis is a string. Cool?"

// 多行
let s3 = """
Hi!
This is a string.
Cool?
"""

// 长度
print(s3.count)
print(s3.isEmpty)

// 拼接
print(s3 + "\nSure!")

// 字符串中插入变量
let i = 1
print("Today is good day, double \(i)\(i)!")

/// 遍历字符串
/// 输出:
/// o
/// n
/// e
for c in "one" {
print(c)
}

// 查找
print(s3.lowercased().contains("cool")) // true

// 替换
let s4 = "one is two"
let newS4 = s4.replacingOccurrences(of: "two", with: "one")
print(newS4)

// 删除空格和换行
let s5 = " Simple line. \n\n "
print(s5.trimmingCharacters(in: .whitespacesAndNewlines))

// 切割成数组
let s6 = "one/two/three"
let a1 = s6.components(separatedBy: "/") // 继承自 NSString 的接口
print(a1) // ["one", "two", "three"]

let a2 = s6.split(separator: "/")
print(a2) // ["one", "two", "three"] 属于切片,性能较 components 更好

// 判断是否是某种类型
let c1: Character = "🤔"
print(c1.isASCII) // false
print(c1.isSymbol) // true
print(c1.isLetter) // false
print(c1.isNumber) // false
print(c1.isUppercase) // false

// 字符串和 Data 互转
let data = Data("hi".utf8)
let s7 = String(decoding: data, as: UTF8.self)
print(s7) // hi

// 字符串可以当作集合来用。
let revered = s7.reversed()
print(String(revered))

Unicode、Character 和 SubString 等内容参见官方字符串文档:Strings and Characters — The Swift Programming Language (Swift 5.1)

字符串字面符号可以参看《String literals in Swift》。

原始字符串

1
2
3
4
5
6
7
8
9
10
11
// 原始字符串在字符串前加上一个或多个#符号。里面的双引号和转义符号将不再起作用了,如果想让转义符起作用,需要在转义符后面加上#符号。
let s8 = #"\(s7)\#(s7) "one" and "two"\n. \#nThe second line."#
print(s8)
/// \(s7)hi "one" and "two"\n.
/// The second line.

// 原始字符串在正则使用效果更佳,反斜杠更少了。
let s9 = "\\\\[A-Z]+[A-Za-z]+\\.[a-z]+"
let s10 = #"\\[A-Z]+[A-Za-z]+\.[a-z]+"#
print(s9) // \\[A-Z]+[A-Za-z]+\.[a-z]+
print(s10) // \\[A-Z]+[A-Za-z]+\.[a-z]+

Swift5.7 String Index 大升级 String Index Overhaul

枚举

Swift的枚举有类的一些特性,比如计算属性、实例方法、扩展、遵循协议等等。

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
49
50
51
52
53
54
55
56
enum E1:String, CaseIterable {
case e1, e2 = "12"
}

// 关联值
enum E2 {
case e1([String])
case e2(Int)
}
let e1 = E2.e1(["one","two"])
let e2 = E2.e2(3)

switch e1 {
case .e1(let array):
print(array)
case .e2(let int):
print(int)
}
print(e2)

// 原始值
print(E1.e1.rawValue)

// 遵循 CaseIterable 协议可迭代
for ie in E1.allCases {
print("show \(ie)")
}

// 递归枚举
enum RE {
case v(String)
indirect case node(l:RE, r:RE)
}

let lNode = RE.v("left")
let rNode = RE.v("right")
let pNode = RE.node(l: lNode, r: rNode)

switch pNode {
case .v(let string):
print(string)
case .node(let l, let r):
print(l,r)
switch l {
case .v(let string):
print(string)
case .node(let l, let r):
print(l, r)
}
switch r {
case .v(let string):
print(string)
case .node(let l, let r):
print(l, r)
}
}

@unknown 用来区分固定的枚举和可能改变的枚举的能力。@unknown 用于防止未来新增枚举属性会进行提醒提示完善每个 case 的处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// @unknown
enum E3 {
case e1, e2, e3
}

func fe1(e: E3) {
switch e {
case .e1:
print("e1 ok")
case .e2:
print("e2 ok")
case .e3:
print("e3 ok")
@unknown default:
print("not ok")
}
}

符合 Comparable 协议的枚举可以进行比较。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Comparable 枚举比较
enum E4: Comparable {
case e1, e2
case e3(i: Int)
case e4
}
let e3 = E4.e4
let e4 = E4.e3(i: 3)
let e5 = E4.e3(i: 2)
let e6 = E4.e1
print(e3 > e4) // true
let a1 = [e3, e4, e5, e6]
let a2 = a1.sorted()
for i in a2 {
print(i.self)
}
/// e1
/// e3(i: 2)
/// e3(i: 3)
/// e4

元组

元组里的值类型可以是不同的。元组可以看成是匿名的结构体。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let t1 = (p1: 1, p2: "two", p3: [1,2,3])
print(t1.p1)
print(t1.p3)

// 类型推导
let t2 = (1, "two", [1,2,3])

// 通过下标访问
print(t2.1) // two

// 分解元组
let (dp1, dp2, _) = t2
print(dp1)
print(dp2)

泛型和协议

泛型可以减少重复代码,是一种抽象的表达方式。where 关键字可以对泛型做约束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func fn<T>(p: T) -> [T] {
var r = [T]()
r.append(p)
return r
}
print(fn(p: "one"))

// 结构体
struct S1<T> {
var arr = [T]()
mutating func add(_ p: T) {
arr.append(p)
}
}

var s1 = S1(arr: ["zero"])
s1.add("one")
s1.add("two")
print(s1.arr) // ["zero", "one", "two"]

关联类型

1
2
3
4
5
6
7
8
9
10
11
12
protocol pc {
associatedtype T
mutating func add(_ p: T)
}

struct S2: pc {
typealias T = String // 类型推导,可省略
var strs = [String]()
mutating func add(_ p: String) {
strs.append(p)
}
}

泛型适用于嵌套类型

1
2
3
4
5
6
7
8
9
10
11
12
struct S3<T> {
struct S4 {
var p: T
}

var p1: T
var p2: S4
}

let s2 = S3(p1: 1, p2: S3.S4(p: 3))
let s3 = S3(p1: "one", p2: S3.S4(p: "three"))
print(s2,s3)

session Embrace Swift genericsDesign protocol interfaces in Swift

swift 5.6 和之前编写泛型接口如下:

1
2
3
4
5
func feed<A>(_ animal: A) where A: Animal

// 👆🏻👇🏻 Equivalents

func feed<A: Animal>(_ animal: A)

swift 5.7 可以这样写:

1
func feed(_ animal: some Animal)

some 关键字可以用于参数和结构类型。some 会保证类型关系,而 any 会持有任意具体类型,删除类型关系。

SE-0347 Type inference from default expressions 扩展 Swift 泛型参数类型的默认值能力。如下代码示例:

1
2
3
4
5
6
func suffledArray<T: Sequence>(from options: T = 1...100) -> [T.Element] {
Array(options.shuffled())
}

print(suffledArray())
print(suffledArray(from: ["one", "two", "three"]))

SE-0341 Opaque Parameter Declarations 使用 some 参数简化泛型参数声明。SE-0328 Structural opaque result types 扩大不透明结果返回类型可以使用的范围。SE-0360 Opaque result types with limited availability 可用性有限的不透明结果类型,比如 if #available(macOS 13.0, *) {} 就可以根据系统不同版本返回不同类型,新版本出现新类型的 View 就可以和以前的 View 类型区别开。

SE-0309 Unlock existentials for all protocols 改进了 existentials 和 泛型的交互。这样就可以更方便的检查 Any 类型的两个值是否相等

any 关键字充当的是类型擦除的助手,是通过告知编译器你使用 existential 作为类型,此语法可兼容以前系统。

SE-0346 Lightweight same-type requirements for primary associated types 引入一种新语法,用于符合泛型参数并通过相同类型要求约束关联类型。SE-0358 Primary Associated Types in the Standard Library 引入主要关联类型概念,并将其带入了标准库。这些关联类型很像泛型,允许开发者将给定关联类型的类型指定为通用约束。

SE-0353 Constrained Existential Types 基于 SE-0309 和 SE-0346 提案,在 existential 类型的上下文中重用轻量关联类型的约束。

SE-0352 Implicitly Opened Existentials 允许 Swift 在很多情况下使用协议调用泛型函数。

Swift 论坛上一个对 any 和 some 关键字语法使用场景的讨论,Do any and some help with “Protocol Oriented Testing” at all?

不透明类型

不透明类型会隐藏类型,让使用者更关注功能。不透明类型和协议很类似,不同的是不透明比协议限定的要多,协议能够对应更多类型。

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
protocol P {
func f() -> String
}
struct S1: P {
func f() -> String {
return "one\n"
}
}
struct S2<T: P>: P {
var p: T
func f() -> String {
return p.f() + "two\n"
}
}
struct S3<T1: P, T2: P>: P {
var p1: T1
var p2: T2
func f() -> String {
return p1.f() + p2.f() + "three\n"
}
}
func someP() -> some P {
return S3(p1: S1(), p2: S2(p: S1()))
}

let r = someP()
print(r.f())

函数调用者决定返回什么类型是泛型,函数自身决定返回什么类型使用不透明返回类型。

Result

Result 类型用来处理错误,特别适用异步接口的错误处理。

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
extension URLSession {
func dataTaskWithResult(
with url: URL,
handler: @escaping (Result<Data, Error>) -> Void
) -> URLSessionDataTask {
dataTask(with: url) { data, _, err in
if let err = err {
handler(.failure(err))
} else {
handler(.success(data ?? Data()))
}
}
}
}

let url = URL(string: "https://ming1016.github.io/")!

// 以前网络请求
let t1 = URLSession.shared.dataTask(with: url) {
data, _, error in
if let err = error {
print(err)
} else if let data = data {
print(String(decoding: data, as: UTF8.self))
}
}
t1.resume()

// 使用 Result 网络请求
let t2 = URLSession.shared.dataTaskWithResult(with: url) { result in
switch result {
case .success(let data):
print(String(decoding: data, as: UTF8.self))
case .failure(let err):
print(err)
}
}
t2.resume()

类型转换

使用 is 关键字进行类型判断, 使用as 关键字来转换成子类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class S0 {}
class S1: S0 {}
class S2: S0 {}

var a = [S0]()
a.append(S1())
a.append(S2())
for e in a {
// 类型判断
if e is S1 {
print("Type is S1")
} else if e is S2 {
print("Type is S2")
}
// 使用 as 关键字转换成子类
if let s1 = e as? S1 {
print("As S1 \(s1)")
} else if let s2 = e as? S2 {
print("As S2 \(s2)")
}
}

类和结构体

类可以定义属性、方法、构造器、下标操作。类使用扩展来扩展功能,遵循协议。类还以继承,运行时检查实例类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class C {
var p: String
init(_ p: String) {
self.p = p
}

// 下标操作
subscript(s: String) -> String {
get {
return p + s
}
set {
p = s + newValue
}
}
}

let c = C("hi")
print(c.p)
print(c[" ming"])
c["k"] = "v"
print(c.p)

结构体

结构体是值类型,可以定义属性、方法、构造器、下标操作。结构体使用扩展来扩展功能,遵循协议。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct S {
var p1: String = ""
var p2: Int
}

extension S {
func f() -> String {
return p1 + String(p2)
}
}

var s = S(p2: 1)
s.p1 = "1"
print(s.f()) // 11

属性

类、结构体或枚举里的变量常量就是他们的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct S {
static let sp = "类型属性" // 类型属性通过类型本身访问,非实例访问
var p1: String = ""
var p2: Int = 1
// cp 是计算属性
var cp: Int {
get {
return p2 * 2
}
set {
p2 = newValue + 2
}
}
// 只有 getter 的是只读计算属性
var rcp: Int {
p2 * 4
}
}
print(S.sp)
print(S().cp) // 2
var s = S()
s.cp = 3
print(s.p2) // 5
print(S().rcp) // 4

willSet 和 didSet 是属性观察器,可以在属性值设置前后插入自己的逻辑处理。

键路径表达式作为函数

1
2
3
4
5
6
7
8
9
10
struct S2 {
let p1: String
let p2: Int
}

let s2 = S2(p1: "one", p2: 1)
let s3 = S2(p1: "two", p2: 2)
let a1 = [s2, s3]
let a2 = a1.map(\.p1)
print(a2) // ["one", "two"]

方法

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
enum E: String {
case one, two, three
func showRawValue() {
print(rawValue)
}
}

let e = E.three
e.showRawValue() // three

// 可变的实例方法,使用 mutating 标记
struct S {
var p: String
mutating func addFullStopForP() {
p += "."
}
}
var s = S(p: "hi")
s.addFullStopForP()
print(s.p)

// 类方法
class C {
class func cf() {
print("类方法")
}
}

static和class关键字修饰的方法类似 OC 的类方法。static 可以修饰存储属性,而 class 不能;class 修饰的方法可以继承,而 static 不能。在协议中需用 static 来修饰。

静态下标方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 静态下标
struct S2 {
static var sp = [String: Int]()

static subscript(_ s: String, d: Int = 10) -> Int {
get {
return sp[s] ?? d
}
set {
sp[s] = newValue
}
}
}

S2["key1"] = 1
S2["key2"] = 2
print(S2["key2"]) // 2
print(S2["key3"]) // 10

自定义类型中实现了 callAsFunction() 的话,该类型的值就可以直接调用。

1
2
3
4
5
6
7
8
9
10
// callAsFunction()
struct S3 {
var p1: String

func callAsFunction() -> String {
return "show \(p1)"
}
}
let s2 = S3(p1: "hi")
print(s2()) // show hi

继承

类能继承另一个类,继承它的方法、属性等。

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
49
50
51
52
// 类继承
class C1 {
var p1: String
var cp1: String {
get {
return p1 + " like ATM"
}
set {
p1 = p1 + newValue
}
}
init(p1: String) {
self.p1 = p1
}
func sayHi() {
print("Hi! \(p1)")
}
}

class C2: C1 {
var p2: String
init(p2: String) {
self.p2 = p2
super.init(p1: p2 + "'s father")
}
}

C2(p2: "Lemon").sayHi() // Hi! Lemon's father

// 重写父类方法
class C3: C2 {
override func sayHi() {
print("Hi! \(p2)")
}
}

C3(p2: "Lemon").sayHi() // Hi! Lemon

// 重写计算属性
class C4: C1 {
override var cp1: String {
get {
return p1 + " like Out of the blade"
}
set {
p1 = p1 + newValue
}
}
}

print(C1(p1: "Lemon").cp1) // Lemon like ATM
print(C4(p1: "Lemon").cp1) // Lemon like Out of the blade

通过 final 关键字可以防止类被继承,final 还可以用于属性和方法。使用 super 关键字指代父类。

函数式

map

map 可以依次处理数组中元素,并返回一个处理后的新数组。

1
2
3
4
5
let a1 = ["a", "b", "c"]
let a2 = a1.map {
"\($0)2"
}
print(a2) // ["a2", "b2", "c2"]

使用 compactMap 可以过滤 nil 的元素。flatMap 会将多个数组合成一个数组返回。

filter

根据指定条件返回

1
2
3
4
5
let a1 = ["a", "b", "c", "call my name"]
let a2 = a1.filter {
$0.prefix(1) == "c"
}
print(a2) // ["c", "call my name"]

reduce

reduce 可以将迭代中返回的结果用于下个迭代中,并,还能让你设个初始值。

1
2
3
4
5
6
7
let a1 = ["a", "b", "c", "call my name.", "get it?"]
let a2 = a1.reduce("Hey u,", { partialResult, s in
// partialResult 是前面返回的值,s 是遍历到当前的值
partialResult + " \(s)"
})

print(a2) // Hey u, a b c call my name. get it?

sorted

排序

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
// 类型遵循 Comparable
let a1 = ["a", "b", "c", "call my name.", "get it?"]
let a2 = a1.sorted()
let a3 = a1.sorted(by: >)
let a4 = a1.sorted(by: <)

print(a2) // Hey u, a b c call my name. get it?
print(a3) // ["get it?", "call my name.", "c", "b", "a"]
print(a4) // ["a", "b", "c", "call my name.", "get it?"]

// 类型不遵循 Comparable
struct S {
var s: String
var i: Int
}
let a5 = [S(s: "a", i: 0), S(s: "b", i: 1), S(s: "c", i: 2)]
let a6 = a5
.sorted { l, r in
l.i > r.i
}
.map {
$0.i
}

print(a6) // [2, 1, 0]

控制流

If

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
// if
let s = "hi"
if s.isEmpty {
print("String is Empty")
} else {
print("String is \(s)")
}

// 三元条件
s.isEmpty ? print("String is Empty again") : print("String is \(s) again")

// if let-else
func f(s: String?) {
if let s1 = s {
print("s1 is \(s1)")
} else {
print("s1 is nothing")
}
// nil-coalescing
let s2 = s ?? "nothing"
print("s2 is \(s2)")
}
f(s: "something")
f(s: nil)

// if case let
enum E {
case c1(String)
case c2([String])

func des() {
switch self {
case .c1(let string):
print(string)
case .c2(let array):
print(array)
}
}
}

E.c1("enum c1").des()
E.c2(["one", "two", "three"]).des()

Guard

更好地处理异常情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// guard
func f1(p: String) -> String {
guard p.isEmpty != true else {
return "Empty string."
}
return "String \(p) is not empty."
}

print(f1(p: "")) // Empty string.
print(f1(p: "lemon")) // String lemon is not empty.

// guard let
func f2(p1: String?) -> String {
guard let p2 = p1 else {
return "Nil."
}
return "String \(p2) is not nil."
}

print(f2(p1: nil)) // Nil.
print(f2(p1: "lemon")) // String lemon is not nil.

遍历

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
let a = ["one", "two", "three"]
for str in a {
print(str)
}

a.forEach { str in
print(str)
}

// 使用下标范围
for i in 0..<10 {
print(i)
}

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

// 使用 enumerated,i 是下标,str 是元素
for (i, str) in a.enumerated() {
print("第\(i + 1)个是:\(str)")
}

// for in where
for str in a where str.prefix(1) == "t" {
print(str)
}

// 字典 for in,遍历是无序的
let dic = [
"one": 1,
"two": 2,
"three": 3
]

for (k, v) in dic {
print("key is \(k), value is \(v)")
}

dic.forEach { (k, v) in
print("key is \(k), value is \(v)")
}

// stride
for i in stride(from: 10, through: 0, by: -2) {
print(i)
}
/*
10
8
6
4
2
0
*/

// 在循环中使用 continue 和 break
for i in 0..<10 {
if i % 2 == 0 {
continue
}
print(i)
}

// 在循环中使用 where
for i in 0..<10 where i % 2 == 0 {
print(i)
}

While

1
2
3
4
5
6
7
8
9
10
11
12
13
// while
var i1 = 10
while i1 > 0 {
print("positive even number \(i1)")
i1 -= 2
}

// repeat while
var i2 = 10
repeat {
print("positive even number \(i2)")
i2 -= 2
} while i2 > 0

使用 break 结束遍历,使用 continue 跳过当前作用域,继续下个循环

Switch

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
49
50
51
52
53
54
55
56
57
58
func f1(pa: String, t:(String, Int)) {
var p1 = 0
var p2 = 10
switch pa {
case "one":
p1 = 1
case "two":
p1 = 2
fallthrough // 继续到下个 case 中
default:
p2 = 0
}
print("p1 is \(p1)")
print("p2 is \(p2)")

// 元组
switch t {
case ("0", 0):
print("zero")
case ("1", 1):
print("one")
default:
print("no")
}
}

f1(pa: "two", t:("1", 1))
/*
p1 is 2
p2 is 0
one
*/

// 枚举
enum E {
case one, two, three, unknown(String)
}

func f2(pa: E) {
var p: String
switch pa {
case .one:
p = "1"
case .two:
p = "2"
case .three:
p = "3"
case let .unknown(u) where Int(u) ?? 0 > 0 : // 枚举关联值,使用 where 增加条件
p = u
case .unknown(_):
p = "negative number"
}
print(p)
}

f2(pa: E.one) // 1
f2(pa: E.unknown("10")) // 10
f2(pa: E.unknown("-10")) // negative number

集合

数组

数组是有序集合

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
var a0: [Int] = [1, 10]
a0.append(2)
a0.remove(at: 0)
print(a0) // [10, 2]

let a1 = ["one", "two", "three"]
let a2 = ["three", "four"]

// 找两个集合的不同
let dif = a1.difference(from: a2) // swift的 diffing 算法在这 http://www.xmailserver.org/diff2.pdf swift实现在 swift/stdlib/public/core/Diffing.swift
for c in dif {
switch c {
case .remove(let o, let e, let a):
print("offset:\(o), element:\(e), associatedWith:\(String(describing: a))")
case .insert(let o, let e, let a):
print("offset:\(o), element:\(e), associatedWith:\(String(describing: a))")
}
}
/*
remove offset:1, element:four, associatedWith:nil
insert offset:0, element:one, associatedWith:nil
insert offset:1, element:two, associatedWith:nil
*/
let a3 = a2.applying(dif) ?? [] // 可以用于添加删除动画
print(a3) // ["one", "two", "three"]

dif 有第三个 case 值 .insert(let offset, let element, let associatedWith) 可以跟踪成对的变化,用于高级动画。

从数组中随机取一个元素

1
print(a0.randomElement() ?? 0)

数组排序

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
// 排序
struct S1 {
let n: Int
var b = true
}

let a4 = [
S1(n: 1),
S1(n: 10),
S1(n: 3),
S1(n: 2)
]
let a5 = a4.sorted { i1, i2 in
i1.n < i2.n
}
for n in a5 {
print(n)
}
/// S1(n: 1)
/// S1(n: 2)
/// S1(n: 3)
/// S1(n: 10)

let a6 = [1,10,4,7,2]
print(a6.sorted(by: >)) // [10, 7, 4, 2, 1]

可以加到数组扩展中,通过扩展约束能够指定特定元素类型的排序,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
extension Array where Element == Int {
// 升序
func intSortedASC() -> [Int] {
return self.sorted(by: <)
}
// 降序
func intSortedDESC() -> [Int] {
return self.sorted(by: <)
}
}

print(a6.intSortedASC()) // 使用扩展增加自定义排序能力

在数组中检索满足条件的元素,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 第一个满足条件了就返回
let a7 = a4.first {
$0.n == 10
}
print(a7?.n ?? 0)

// 是否都满足了条件
print(a4.allSatisfy { $0.n == 1 }) // false
print(a4.allSatisfy(\.b)) // true

// 找出最大的那个
print(a4.max(by: { e1, e2 in
e1.n < e2.n
}) ?? S1(n: 0))
// S1(n: 10, b: true)

// 看看是否包含某个元素
print(a4.contains(where: {
$0.n == 7
}))
// false

一些切割数组的方法。

1
2
3
4
5
6
7
// 切片
// 取前3个,并不是直接复制,对于大的数组有性能优势。
print(a6[..<3]) // [1, 10, 4] 需要做越界检查
print(a6.prefix(30)) // [1, 10, 4, 7, 2] 不需要做越界检查,也是切片,性能一样

// 去掉前3个
print(a6.dropFirst(3)) // [7, 2]

prefix(while:) 和 drop(while:) 方法,顺序遍历执行闭包里的逻辑判断,满足条件就返回,遇到不匹配就会停止遍历。prefix 返回满足条件的元素集合,drop 返回停止遍历之后那些元素集合。

1
2
3
4
5
6
7
8
9
let a8 = [8, 9, 20, 1, 35, 3]
let a9 = a8.prefix {
$0 < 30
}
print(a9) // [8, 9, 20, 1]
let a10 = a8.drop {
$0 < 30
}
print(a10) // [35, 3]

比 filter 更高效的删除元素的方法 removeAll

1
2
3
4
5
6
7
8
9
10
// 删除所有不满足条件的元素
var a11 = [1, 3, 5, 12, 25]
a11.removeAll { $0 < 10 }
print(a11) // [12, 25]

// 创建未初始化的数组
let a12 = (0...4).map { _ in
Int.random(in: 0...5)
}
print(a12) // [0, 3, 3, 2, 5] 随机

#if 用于后缀表达式

1
2
3
4
5
6
7
8
// #if 用于后缀表达式
let a13 = a11
#if os(iOS)
.count
#else
.reduce(0, +)
#endif
print(a13) //37

Sets

Set 是无序集合,元素唯一

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
let s0: Set<Int> = [2, 4]
let s1: Set = [2, 10, 6, 4, 8]
let s2: Set = [7, 3, 5, 1, 9, 10]

let s3 = s1.union(s2) // 合集
let s4 = s1.intersection(s2) // 交集
let s5 = s1.subtracting(s2) // 非交集部分
let s6 = s1.symmetricDifference(s2) // 非交集的合集
print(s3) // [4, 2, 1, 7, 3, 10, 8, 9, 6, 5]
print(s4) // [10]
print(s5) // [8, 4, 2, 6]
print(s6) // [9, 1, 3, 4, 5, 2, 6, 8, 7]

// s0 是否被 s1 包含
print(s0.isSubset(of: s1)) // true
// s1 是否包含了 s0
print(s1.isSuperset(of: s0)) // true

let s7: Set = [3, 5]
// s0 和 s7 是否有交集
print(s0.isDisjoint(with: s7)) // true

// 可变 Set
var s8: Set = ["one", "two"]
s8.insert("three")
s8.remove("one")
print(s8) // ["two", "three"]

字典

字典是无序集合,键值对应。

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
49
50
51
52
var d1 = [
"k1": "v1",
"k2": "v2"
]
d1["k3"] = "v3"
d1["k4"] = nil

print(d1) // ["k2": "v2", "k3": "v3", "k1": "v1"]

for (k, v) in d1 {
print("key is \(k), value is \(v)")
}
/*
key is k1, value is v1
key is k2, value is v2
key is k3, value is v3
*/

if d1.isEmpty == false {
print(d1.count) // 3
}

// mapValues
let d2 = d1.mapValues {
$0 + "_new"
}
print(d2) // ["k2": "v2_new", "k3": "v3_new", "k1": "v1_new"]

// 对字典的值或键进行分组
let d3 = Dictionary(grouping: d1.values) {
$0.count
}
print(d3) // [2: ["v1", "v2", "v3"]]

// 从字典中取值,如果键对应无值,则使用通过 default 指定的默认值
d1["k5", default: "whatever"] += "."
print(d1["k5"] ?? "") // whatever.
let v1 = d1["k3", default: "whatever"]
print(v1) // v3

// compactMapValues() 对字典值进行转换和解包。可以解可选类型,并去掉 nil 值
let d4 = [
"k1": 1,
"k2": 2,
"k3": nil
]
let d5 = d4.mapValues { $0 }
let d6 = d4.compactMapValues{ $0 }
print(d5)
// ["k3": nil, "k1": Optional(1), "k2": Optional(2)]
print(d6)
// ["k1": 1, "k2": 2]

操作符

赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let i1 = 1
var i2 = i1
i2 = 2
print(i2) // 2

i2 += 1
print(i2) // 3

i2 -= 2
print(i2) // 1

i2 *= 10
print(i2) // 10

i2 /= 2
print(i2) // 5

计算符

1
2
3
4
5
6
7
let i1 = 1
let i2 = i1
print((i1 + i2 - 1) * 10 / 2 % 3) // 2
print("i" + "1") // i1

// 一元运算符
print(-i1) // -1

比较运算符

遵循 Equatable 协议可以使用 == 和 != 来判断是否相等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
print(1 > 2) // false

struct S: Equatable {
var p1: String
var p2: Int
}
let s1 = S(p1: "one", p2: 1)
let s2 = S(p1: "two", p2: 2)
let s3 = S(p1: "one", p2: 2)
let s4 = S(p1: "one", p2: 1)

print(s1 == s2) // false
print(s1 == s3) // false
print(s1 == s4) // true

类需要实现 == 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class C: Equatable {
var p1: String
var p2: Int
init(p1: String, p2: Int) {
self.p1 = p1
self.p2 = p2
}

static func == (l: C, r: C) -> Bool {
return l.p1 == r.p1 && l.p2 == r.p2
}
}

let c1 = C(p1: "one", p2: 1)
let c2 = C(p1: "one", p2: 1)
print(c1 == c2)
// 元组比较
// 会先比较第一个数,第一个无法比较才会比较第二个数
// 字符串比较和字母大小还有长度有关。先比较字母大小,在比较长度
("apple", 1) < ("apple", 2) // true
("applf", 1) < ("apple", 2) // false
("appl", 2) < ("apple", 1) // true
("appm", 2) < ("apple", 1) // false

三元

简化 if else 写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// if else
func f1(p: Int) {
if p > 0 {
print("positive number")
} else {
print("negative number")
}
}

// 三元
func f2(p: Int) {
p > 0 ? print("positive number") : print("negative number")
}

f1(p: 1)
f2(p: 1)

coalescing

简化 if let else 写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// if else
func f1(p: Int?) {
if let i = p {
print("p have value is \(i)")
} else {
print("p is nil, use defalut value")
}
}

// 使用 ??
func f2(p: Int?) {
let i = p ?? 0
print("p is \(i)")
}

范围

简化的值范围表达方式。

1
2
3
4
5
6
7
8
9
// 封闭范围
for i in 0...10 {
print(i)
}

// 半开范围
for i in 0..<10 {
print(i)
}
1
2
3
// 单侧区间
let nums = [5,6,7,8]
print(nums[2...]) // 7 8

逻辑

1
2
3
4
5
6
let i1 = -1
let i2 = 2

if i1 != i2 && (i1 < 0 || i2 < 0) {
print("i1 and i2 not equal, and one of them is negative number.")
}

恒等

恒等返回是否引用了相同实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class C {
var p: String
init(p: String) {
self.p = p
}
}

let c1 = C(p: "one")
let c2 = C(p: "one")
let c3 = c1

print(c1 === c2) // false
print(c1 === c3) // true
print(c1 !== c2) // true

运算符

位运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let i1: UInt8 = 0b00001111
let i2 = ~i1 // Bitwise NOT Operator(按位取反运算符),取反

let i3: UInt8 = 0b00111111
let i4 = i1 & i3 // Bitwise AND Operator(按位与运算符),都为1才是1
let i5 = i1 | i3 // Bitwise OR Operator(按位或运算符),有一个1就是1
let i6 = i1 ^ i3 // Bitwise XOR Operator(按位异或运算符),不同为1,相同为0

print(i1,i2,i3,i4,i5,i6)

// << 按位左移,>> 按位右移
let i7 = i1 << 1
let i8 = i1 >> 2
print(i7,i8)

溢出运算符,有 &+、&- 和 &*

1
2
3
4
5
6
7
8
9
10
var i1 = Int.max
print(i1) // 9223372036854775807
i1 = i1 &+ 1
print(i1) // -9223372036854775808
i1 = i1 &+ 10
print(i1) // -9223372036854775798

var i2 = UInt.max
i2 = i2 &+ 1
print(i2) // 0

运算符函数包括前缀运算符、后缀运算符、复合赋值运算符以及等价运算符。另,还可以自定义运算符,新的运算符要用 operator 关键字进行定义,同时要指定 prefix、infix 或者 postfix 修饰符。

Swift规范

参考:

多用静态特性。swift 在编译期间所做的优化比 OC 要多,这是由于他的静态派发、泛型特化、写时复制这些静态特性决定的。另外通过 final 和 private 这样的表示可将动态特性转化为静态方式,编译开启 WMO 可以自动推导出哪些动态派发可转化为静态派发。

如何避免崩溃?

  • 字典:用结构体替代
  • Any:可用泛型或关联关联类型替代
  • as? :少用 AnyObject,多用泛型或不透明类型
  • !:要少用

好的实践?

  • 少用继承,多用 protocol
  • 多用 extension 对自己代码进行管理

Swift书单

  • 《Thinking in SwiftUI》
  • 《Swift 进阶》
  • 《函数式Swift》
  • 《深入解析Mac OS X & iOS操作系统》
  • 《LLVM Techniques, Tips, and Best Practices Clang and Middle-End Libraries》
  • 《Learn LLVM 12》
  • 《Crafting Interpreters》
  • 《TCP/IP Illustrated》
  • 《松本行弘的程序世界》
  • 《现代操作系统》
  • 《深入理解计算机系统》
  • 《程序员的自我修养》
  • 《Head First 设计模式》