【iOS】Swift基础语法(简介、语法基础、基础类型介绍)

旨在快速打通iOS开发流程

By yesmore on 2022-11-24
阅读时间 31 分钟
文章共 7.1k
阅读量

本章内容概览:Swift简介、Swift语法基础、Swift基础类型介绍(数字、布尔、元组、字符串、枚举、泛型…)

下一节:【iOS】02_Swift基础语法(类与结构体、函数式)

同系列文章请查看:快乐码元 - iOS篇

学什么?

本系列文章旨在快速入门 iOS (Swift) 开发,从Swift语法开始讲解,到使用 SwfitUI 实战演练,适合有编程经验的程序员上手实践,也适合零基础 iOS 开发。

怎么学?

系列文章整理自互联网,会介绍基本知识以及整理一些在不同学习阶段的网站、资料分享,包含iOS开发相关官方文档、活跃社区、开发资源、常用工具等。

文章中的代码可以在 Online Swift Playground 在线平台编译运行。

下面直接进入正题。

Swift简介

官网介绍:一种强大但极易学习的编程语言。

Swift 是一种强大直观的编程语言,适用于 iOS、iPadOS、macOS、Apple tvOS 和 watchOS。编写 Swift 代码的过程充满了乐趣和互动。Swift 语法简洁,但表现力强,更包含了开发者喜爱的现代功能。Swift 代码从设计上保证安全,并能开发出运行快如闪电的软件。

  • Swift是苹果于2014年WWDC(苹果开发者大会)发布的新开发语言,可与Objective-C共同运行于MAC OS和iOS平台,用于搭建基于苹果平台的应用程序。
  • Swift 使用自动引用计数(Automatic Reference Counting, ARC)来简化内存管理
  • Swift Foundation框架可无缝对接现有的 Cocoa 框架,并且兼容 Objective-C 代码,支持面向过程编程和面向对象编程,Swift也是一门类型安全的语言。
  • Swift中范型是用来使代码能安全工作,可以在函数数据和普通数据类型中使用,例如类、结构体或枚举。范型可以解决代码复用的问题
  • Swift的访问权限变更(新增了两种访问权限,权限更细化)

社区评价

  • 拥抱Swift吧,Objective-C已经是过去时
  • Swift非常棒,但如果偏执于一门语言便会埋没你的才华。
  • 对新手很友好,playground,小学生也可以学编程,对熟悉语法这种比较无聊的事情还蛮舒服的。Swift可能是唯一能在iOS 上编译的语言。
  • 简洁的语法,性能较好报错精准、定义变量简单、可视化互动效果、函数式编程的支持(Map、FlatMap、Filter、Reduce等函数方法)
  • 推广度还不全面,资源少、第三方库的支持不够多、上线方式

Ⅰ.语法基础

本小节将快速浏览一遍Swift基本语法特性

1.变量

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

1
2
3
var v1:String = "hi" // 标注类型
var v2 = "类型推导"
let l1 = "标题" // 常量
1
2
3
4
5
6
class a {
let p1 = 3
var p2: Int {
p1 * 3
}
}

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

  • 变量名可以由字母,数字和下划线组成。

  • 变量名需要以字母或下划线开始。

  • Swift 是一个区分大小写的语言,所以字母大写与小写是不一样的。

  • 变量名也可以使用简单的 Unicode 字符

2.print

控制台打印值

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

3.注释

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

4.可选 ?, !

Swift 的可选(Optional)类型,用于处理值缺失的情况。可选表示"那儿有一个值,并且它等于 x “或者"那儿没有值”。

Swfit语言定义后缀?作为命名类型Optional的简写,换句话说,以下两种声明是相等的:

1
2
var optionalInteger: Int?
var optionalInteger: Optional<Int>

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

1
2
3
var o: Int? = nil
let i = o ?? 0
// ts:这个我熟

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)

强制解析

当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号(!)来获取值。这个感叹号表示"我知道这个可选有值,请使用它。"这被称为可选值的强制解析(forced unwrapping)。

1
2
3
4
5
6
7
8
9
10
11
12
var myString:String?

myString = "Hello, Swift!"

if myString != nil {
// 强制解析
print( myString! )
}else{
print("myString 值为 nil")
}

// > Hello, Swift!

使用!来获取一个不存在的可选值会导致运行时错误。使用!来强制解析值之前,一定要确定可选包含一个非nil的值。

自动解析

你可以在声明可选变量时使用感叹号(!)替换问号(?)。这样可选变量在使用时就不需要再加一个感叹号(!)来获取值,它会自动解析。

1
2
3
4
5
6
7
8
9
10
11
var myString:String!

myString = "Hello, Swift!"

if myString != nil {
print(myString)
}else{
print("myString 值为 nil")
}

// > Hello, Swift!

5.函数

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

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 标识的代码块会在函数返回之前执行。

如果你不确定返回的元组一定不为nil,那么你可以返回一个可选的元组类型。

你可以通过在元组类型的右括号后放置一个问号来定义一个可选元组,例如(Int, Int)?或(String, Int, Bool)?

5.1 可变参数

函数在 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]

5.2 函数重载

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

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

5.3 函数参数名称

函数参数都有一个外部参数名和一个局部参数名。

  • 局部参数名: 局部参数名在函数的实现内部使用
  • 外部参数名: 你可以在局部参数名前指定外部参数名,中间以空格分隔,外部参数名用于在函数调用时传递给函数的参数。
1
2
3
4
5
6
7
8
9
func pow(firstArg a: Int, secondArg b: Int) -> Int {
var res = a
for _ in 1..<b {
res = res * a
}
print(res)
return res
}
pow(firstArg:5, secondArg:3)

5.4 常量,变量及 I/O 参数

一般默认在函数中定义的参数都是常量参数,也就是这个参数你只可以查询使用,不能改变它的值。

如果想要声明一个变量参数,可以在参数定义前加 inout 关键字,这样就可以改变这个参数的值了。

例如:

1
func  getName(_ name: inout String).........

此时这个 name 值可以在函数中改变。

一般默认的参数传递都是传值调用的,而不是传引用。所以传入的参数在函数内改变,并不影响原来的那个参数。传入的只是这个参数的副本

当传入的参数作为输入输出参数时,需要在参数名前加 & 符,表示这个值可以被函数修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}


var x = 1
var y = 5
swapTwoInts(&x, &y)
print("x 现在的值 \(x), y 现在的值 \(y)")

// > x 现在的值 5, y 现在的值 1

6.闭包

闭包也可以叫做 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)

7.访问控制

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

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

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

8.Regex

标准库多了个 Regex<Output> 类型,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、UInt、Float、Double
  • 布尔值:Bool
  • 字符串:String
  • 字符:Character
  • 可选类型:Optional

判断是否是某种类型

1
2
3
4
5
6
let c1: Character = "🤔"
print(c1.isASCII) // false
print(c1.isSymbol) // true
print(c1.isLetter) // false
print(c1.isNumber) // false
print(c1.isUppercase) // false

1.数字

数字的类型有 Int、UInt、Float 和 Double

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 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

字面量:

1
2
3
4
5
6
// 字面量
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:) 方法检查一个数字是否是另一个数字的倍数:

1
2
let i3 = 36
print(i3.isMultiple(of: 9)) // true

处理数字有 floorceilround

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

2.布尔数 Bool

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

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

3.元组 (a, b, c)

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

1
2
3
4
5
6
7
8
9
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

分解元组:

1
2
3
let (dp1, dp2, _) = t2 // 像不像ts的解构赋值
print(dp1)
print(dp2)

4.字符串

字符串可能是你经常接触的一种数据类型,其重要性不言而喻,这里将介绍一些操作字符串的常用内置方法。

1
let s1 = "Hi! This is a string. Cool?"

4.1 转义符

\n 表示换行、 \0 空字符)、\t 水平制表符 、\n 换行符、\r 回车符

1
let s2 = "Hi!\nThis is a string. Cool?"

4.2 多行

1
2
3
4
5
let s3 = """
Hi!
This is a string.
Cool?
"""

4.3 长度

1
2
print(s3.count)
print(s3.isEmpty)

4.4 拼接

1
print(s3 + "\nSure!")

4.5 字符串中插入变量

1
2
let i = 1
print("Today is good day, double \(i)\(i)!")

4.6 遍历字符串

1
2
3
4
5
6
7
for c in "one" {
print(c)
}
/// 输出:
/// o
/// n
/// e

4.7 查找

1
print(s3.lowercased().contains("cool")) // true

4.8 替换

1
2
3
let s4 = "one is two"
let newS4 = s4.replacingOccurrences(of: "two", with: "one")
print(newS4)

4.9 删除空格和换行

1
2
let s5 = " Simple line. \n\n  "
print(s5.trimmingCharacters(in: .whitespacesAndNewlines))

4.10 切割成数组

1
2
3
4
5
6
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 更好

4.11 字符串和 Data 互转

1
2
3
let data = Data("hi".utf8)
let s7 = String(decoding: data, as: UTF8.self)
print(s7) // hi

4.12 字符串可以当作集合来用

1
2
let revered = s7.reversed()
print(String(revered))

4.13 原始字符串

原始字符串在字符串前加上一个或多个#符号。里面的双引号和转义符号将不再起作用了,如果想让转义符起作用,需要在转义符后面加上#符号:

1
2
3
4
let s8 = #"\(s7)\#(s7) "one" and "two"\n. \#nThe second line."#
print(s8)
/// \(s7)hi "one" and "two"\n.
/// The second line.

原始字符串在正则使用效果更佳,反斜杠更少了:

1
2
3
4
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]+

小结:以上就是字符串相关知识点,Unicode、Character 和 SubString 等内容参见官方字符串文档:Strings and Characters — The Swift Programming Language (Swift 5.1)

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

5.枚举

枚举简单的说也是一种数据类型,只不过是这种数据类型只包含自定义的特定数据,它是一组有共同特性的数据的集合。

Swift 的枚举类似于 Objective C 和 C 的结构,枚举的功能为:

  • 它声明在类中,可以通过实例化类来访问它的值。
  • 枚举也可以定义构造函数(initializers)来提供一个初始成员值;可以在原始的实现基础上扩展它们的功能。
  • 可以遵守协议(protocols)来提供标准的功能。

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

5.1 语法

Swift 中使用 enum 关键词来创建枚举并且把它们的整个定义放在一对大括号内:

1
2
3
enum enumname {
// 枚举定义放在这里
}
1
2
3
4
5
6
7
8
9
10
11
12
13
// 定义枚举
enum DaysofaWeek {
case Sunday
case Monday
case TUESDAY
case WEDNESDAY
case THURSDAY
case FRIDAY
case Saturday
}

var weekDay = DaysofaWeek.THURSDAY
weekDay = .THURSDAY

枚举中定义的值(如 SundayMonday……Saturday)是这个枚举的成员值(或成员)。case关键词表示一行新的成员值将被定义。

注意: 和 C 和 Objective-C 不同,Swift 的枚举成员在被创建时不会被赋予一个默认的整型值。在上面的*DaysofaWeek例子中,SundayMonday……Saturday不会隐式地赋值为01……6。相反,这些枚举成员本身就有完备的值,这些值是已经明确定义好的DaysofaWeek*类型。

weekDay的类型可以在它被DaysofaWeek的一个可能值初始化时推断出来。一旦weekDay被声明为一个DaysofaWeek,你可以使用一个缩写语法(.)将其设置为另一个DaysofaWeek的值:

1
var weekDay = .THURSDAY 

weekDay的类型已知时,再次为其赋值可以省略枚举名。使用显式类型的枚举值可以让代码具有更好的可读性。

5.2 关联值

枚举可分为关联值与原始值。

关联值与原始值的区别:

关联值 原始值
不同数据类型 相同数据类型
实例: enum {10,0.8,“Hello”} 实例: enum {10,35,50}
值的创建基于常量或变量 预先填充的值
关联值是当你在创建一个基于枚举成员的新常量或变量时才会被设置,并且每次当你这么做得时候,它的值可以是不同的。 原始值始终是相同的

以下实例中定义一个名为 Student 的枚举类型,它可以是 Name 的一个字符串(String),或者是 Mark 的一个关联值(Int,Int,Int)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum Student{
case Name(String)
case Mark(Int,Int,Int)
}
var studDetails = Student.Name("Runoob")
var studMarks = Student.Mark(98,97,95)
switch studMarks {
case .Name(let studName):
print("学生的名字是: \(studName)。")
case .Mark(let Mark1, let Mark2, let Mark3):
print("学生的成绩是: \(Mark1),\(Mark2),\(Mark3)。")
}

// > 学生的成绩是: 98,97,95。

5.3 原始值

原始值可以是字符串,字符,或者任何整型值或浮点型值。每个原始值在它的枚举声明中必须是唯一的。

在原始值为整数的枚举时,不需要显式的为每一个成员赋值,Swift会自动为你赋值

例如,当使用整数作为原始值时,隐式赋值的值依次递增1。如果第一个值没有被赋初值,将会被自动置为0。

1
2
3
4
5
6
7
8
9
10
import Cocoa

enum Month: Int {
case January = 1, February, March, April, May, June, July, August, September, October, November, December
}

let yearMonth = Month.May.rawValue
print("数字月份为: \(yearMonth)。")

// > 数字月份为: 5。

5.4 应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 关联值
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)
1
2
3
4
// 遵循 CaseIterable 协议可迭代
for ie in E1.allCases {
print("show \(ie)")
}

递归枚举

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

6.泛型

菜鸟详解:https://www.runoob.com/swift/swift-generics.html

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

Swift 的数组和字典类型都是泛型集。

你可以创建一个Int数组,也可创建一个String数组,或者甚至于可以是任何其他 Swift 的类型数据数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 定义一个交换两个变量的函数
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}

var numb1 = 100
var numb2 = 200

print("交换前数据: \(numb1)\(numb2)")
swapTwoValues(&numb1, &numb2)
print("交换后数据: \(numb1)\(numb2)")

var str1 = "A"
var str2 = "B"

print("交换前数据: \(str1)\(str2)")
swapTwoValues(&str1, &str2)
print("交换后数据: \(str1)\(str2)")
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"]

6.1 扩展泛型类型

当你扩展一个泛型类型的时候(使用 extension 关键字),你并不需要在扩展的定义中提供类型参数列表。更加方便的是,原始类型定义中声明的类型参数列表在扩展里是可以使用的,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。

下面的例子扩展了泛型类型 Stack,为其添加了一个名为 topItem 的只读计算型属性,它将会返回当前栈顶端的元素而不会将其从栈中移除:

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
struct Stack<Element> {
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}

extension Stack {
var topItem: Element? {
return items.isEmpty ? nil : items[items.count - 1]
}
}

var stackOfStrings = Stack<String>()
print("字符串元素入栈: ")
stackOfStrings.push("google")
stackOfStrings.push("runoob")

if let topItem = stackOfStrings.topItem {
print("栈中的顶部元素是:\(topItem).")
}

print(stackOfStrings.items)

实例中 topItem 属性会返回一个 Element 类型的可选值。当栈为空的时候,topItem 会返回 nil;当栈不为空的时候,topItem 会返回 items 数组中的最后一个元素。

以上程序执行输出结果为:

1
2
3
字符串元素入栈: 
栈中的顶部元素是:runoob.
["google", "runoob"]

6.2 关联类型

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)
}
}

6.3 泛型适用于嵌套类型

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)

7.不透明类型

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

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())

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

8.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()

9.类型转换

使用 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)")
}
}

10.类型别名

语法:

1
typealias newname = type

例:

1
2
3
typealias Feet = Int
var distance: Feet = 100
print(distance)

10.1 类型安全

Swift 是一个类型安全(type safe)的语言。

由于 Swift 是类型安全的,所以它会在编译你的代码时进行类型检查(type checks),并把不匹配的类型标记为错误。这可以让你在开发的时候尽早发现并修复错误。

1
2
3
4
5
6
7
var varA = 42
varA = "This is hello"
print(varA)

报错:
error: cannot assign value of type 'String' to type 'Int'
varA = "This is hello"

10.2 类型推断

当你要处理不同类型的值时,类型检查可以帮你避免错误。然而,这并不是说你每次声明常量和变量的时候都需要显式指定类型。

如果你没有显式指定类型,Swift 会使用类型推断(type inference)来选择合适的类型。

例如,如果你给一个新常量赋值42并且没有标明类型,Swift 可以推断出常量类型是Int,因为你给它赋的初始值看起来像一个整数:

1
2
let meaningOfLife = 42
// meaningOfLife 会被推测为 Int 类型

同理,如果你没有给浮点字面量标明类型,Swift 会推断你想要的是Double:

1
2
let pi = 3.14159
// pi 会被推测为 Double 类型

当推断浮点数的类型时,Swift 总是会选择Double而不是Float。

如果表达式中同时出现了整数和浮点数,会被推断为Double类型:

1
2
let anotherPi = 3 + 0.14159
// anotherPi 会被推测为 Double 类型

参考资料:


Tips: Please indicate the source and original author when reprinting or quoting this article.