Swift Basics

Swift Basics #1

1. 控制台打印输出

在开发过程中,我们经常需要将信息输出到底部的控制台(Console),以便获取代码的反馈、检查运行状态或进行调试。在 Swift 中,使用 print() 函数来完成这一操作。

1
2
3
4
5
6
// 直接打印字符串
print("Hello playground")

// 打印变量的内容
var greeting = "Hello World"
print(greeting)

2. 代码注释

注释是专门写给开发者自己或其他阅读代码的人看的,应用程序在编译和运行时会完全忽略这些内容。写注释不仅可以当作学习笔记,更是良好编程习惯的一部分。Swift 中有两种写注释的方法:

  • 单行注释: 使用两个正斜杠 //
  • 多行注释:/* 开头,并以 */ 结尾。
1
2
3
4
5
6
7
// 这是一个单行注释,通常用于简短的说明

/*
这是一个多行注释
当你有很长的一段话需要记录时
可以让注释跨越多个代码行
*/

3. 命名规范:驼峰命名法

在声明变量或常量时,不同的编程语言有不同的命名偏好。在 Swift 中,标准且被所有 Swift 开发者广泛接受的规范是驼峰命名法 (Camel Case)规则是:名称的第一个单词全部小写,之后紧跟的每一个单词的首字母都要大写。

1
2
3
4
5
6
7
8
9
// ✅ 正确推荐:驼峰命名法 (Camel Case)
let firstGreeting = "Hello World"
let thisIsMyFirstGreeting = "Hello World"

// ❌ 不推荐:蛇形命名法 (Snake Case) - 通常用于 Python 等其他语言
let this_is_what_snake_case_looks_like = "Hello World"

// ❌ 不推荐:帕斯卡命名法 (Pascal Case) - 首字母也大写了
let ThisIsWhatPascalCaseLooksLike = "Hello World"

Swift Basics #2

1. 字符串类型

字符串用于表示常规的文本内容。在 Swift 中,字符串必须被包裹在双引号中。如果没有双引号,编译器会将其误认为是代码的一部分从而报错。

1
2
3
4
5
// 声明一个明确类型为 String 的常量
let myFirstItem: String = "Hello World"

// 声明时也可以省略类型,Swift 会自动推断为 String
let myTitle = "Hello World"

2. 布尔类型

布尔值用于表示逻辑上的“真”或“假”。它只能是两个值之一:truefalse。在控制逻辑(例如“如果为真则执行某操作”)中非常有用。千万不要将布尔值用双引号包裹,否则它就变成了普通的字符串文本。

1
2
3
4
5
6
// 声明一个明确类型为 Bool 的常量
let mySecondItem: Bool = true
let myThirdItem: Bool = false

// ❌ 错误示范:这只是一个内容叫 "true" 的字符串,而不是布尔值
// let myFourthItem: String = "true"

3. 类型安全

Swift 是一门“类型安全”的编程语言。这意味着,如果你显式地告诉编译器某个变量或常量应该是什么类型,你就不能再给它赋予其他类型的值,否则代码将无法编译运行。这一特性可以极大程度地避免应用在运行中崩溃。一旦某个数据被定义为某种类型,它的类型就永远无法被更改。

1
2
3
4
5
// ✅ 正确示范:类型匹配
let myFifthItem: Bool = true

// ❌ 错误示范:试图将 String 赋值给明确声明为 Bool 的常量,编译器会报错
// let mySixthItem: Bool = "Hello World"

4. 日期类型

Swift 内置了专门用于处理时间和日期的类型。它可以用来获取当前时间,或者记录应用中某个对象的创建和保存时间。

1
2
// 声明一个日期类型,并使用 Date() 获取当前的时间
let myFirstDate: Date = Date()

5. 数字类型

在 Swift 中,数字有很多种细分的类型。最常用的包括:

  • Int: 整数,用于表示没有小数点的纯整数(例如 1, 2, 100)。
  • Double: 双精度浮点数,支持小数点,常用于复杂的数学计算。
  • CGFloat: 同样支持小数点,通常在处理用户界面(UI)元素(例如:屏幕边距为 15 像素,字体大小为 42)时使用。
1
2
3
4
5
6
7
8
// Int 整数类型
let myFirstNumber: Int = 1

// Double 包含小数的类型(数学计算)
let mySecondNumber: Double = 1.0

// CGFloat 包含小数的类型(UI 构建)
let myThirdNumber: CGFloat = 1.0

Swift Basics #3

1. 常量与变量的区别

在 Swift 中,我们可以使用 letvar 来声明数据。

  • 常量 (let): 一旦被赋值,它的值就永远不能再被改变。在编程中,我们应该尽可能多地使用常量,以保证代码的安全性和可预测性。
  • 变量 (var): 它的值是可变的,在应用的运行周期内,你可以随时修改它的内容。只有当你明确知道这个数据未来需要被修改时,才应该使用变量。
1
2
3
4
5
6
7
// 使用 let 声明常量,值不可修改
let someConstant: Bool = true
// someConstant = false // ❌ 报错:常量的值无法被改变

// 使用 var 声明变量,值可以随时修改
var someVariable: Bool = true
someVariable = false // ✅ 正确:变量的值可以被改变

2. 类型的不可变性

虽然变量(var)的值可以被改变,但它的数据类型是永远不能被改变的。一旦一个变量被声明为某种特定类型(例如 Double),你赋予它的新值也必须是相同的类型,Swift 的类型安全机制会严格检查这一点。

1
2
3
4
5
6
7
8
9
// 声明一个 Double 类型的变量
var myNumber: Double = 1.0

// 修改它的值为另一个 Double,完全没问题
myNumber = 2.5
myNumber = 100.0

// ❌ 报错:不能将一个 Bool 值赋值给声明为 Double 的变量
// myNumber = true

3. 基础条件判断语句

在代码中,我们经常需要根据某个条件是否成立来决定执行哪段代码。ifelse 是最常用的条件判断语句。如果 if 后面的条件为真,则执行大括号里的代码;如果不为真,则执行 else 里的代码。

1
2
3
4
5
6
7
8
9
var userIsPremium: Bool = false

if userIsPremium == true {
// 如果为 true,执行这里的代码
print("User is premium")
} else {
// 如果为 false,执行这里的代码
print("User is not premium")
}

4. 布尔值判断的简写形式

在判断布尔值时,Swift 开发者通常会使用更简洁的写法,而不是写出完整的 == true== false

  • 检查是否为 true,可以直接写变量名。
  • 检查是否为 false,可以在变量名前面加一个英文感叹号 !(表示“非”)。
1
2
3
4
5
6
7
8
9
10
11
var isPremium: Bool = true

// ✅ 简写:判断是否为 true
if isPremium {
print("用户是高级会员")
}

// ✅ 简写:判断是否为 false (感叹号表示逻辑非)
if !isPremium {
print("用户不是高级会员")
}

Swift Basics #4

1. 数学运算符与简写语法

Swift 支持基础的加(+)、减(-)、乘(*)、除(/)等算术运算。为了简化代码,Swift 提供了组合赋值运算符,用来直接在原变量的基础上进行加减乘除并重新赋值。

1
2
3
4
5
6
7
8
9
10
var likeCount: Double = 5.0

// 基础写法:将当前值加 1 再赋值给自身
likeCount = likeCount + 1

// ✅ 简写语法:效果等同于上面一行代码,但更简洁
likeCount += 1
likeCount -= 1
likeCount *= 1.5
likeCount /= 2.0

2. 运算优先级

与我们在数学课上学到的规则一样,Swift 中的代码运算也严格遵守“先乘除,后加减”的优先级顺序(PEMDAS 规则)。你可以使用括号 () 来改变运算的执行顺序。

1
2
3
4
5
6
7
8
var mathResult: Double = 0.0
var count: Double = 5.0

// 乘法会优先计算:先算 1 * 1.5,然后再用 count 减去结果
mathResult = count - 1 * 1.5

// 使用括号改变优先级:先计算括号内的减法,再将结果乘以 1.5
mathResult = (count - 1) * 1.5

3. 比较运算符

比较运算符常用于 if 语句中,用来对比两个数据的大小或验证是否相等。常用运算符包括:等于(==)、不等于(!=)、大于(>)、大于等于(>=)、小于(<)和小于等于(<=)。

1
2
3
4
5
6
7
8
9
10
11
12
13
let viewCount = 100

if viewCount == 100 {
print("观看次数正好是 100")
}

if viewCount != 50 {
print("观看次数不是 50")
}

if viewCount >= 100 {
print("观看次数大于或等于 100")
}

4. 逻辑运算符

当我们需要在同一个 if 语句中同时检查多个条件时,可以使用逻辑运算符来组合它们:

  • 逻辑与 (&&): 只有当符号两边的条件都为真时,整体才为真。
  • 逻辑或 (||): 只要符号两边的条件中有一个为真,整体就为真。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let comments = 10
let isPremiumUser = true
let likes = 4

// 使用 && :点赞大于3 且 评论大于0 时执行
if likes > 3 && comments > 0 {
print("帖子互动很好")
}

// 使用 || :点赞大于5 或者 用户是高级会员 时执行
if likes > 5 || isPremiumUser {
print("满足推荐条件")
}

// 组合使用:利用括号将逻辑分组,使得复杂逻辑关系更加清晰
if (likes > 3 && comments > 0) || isPremiumUser {
print("执行复杂逻辑")
}

5. 多重条件判断语句

如果单个 ifelse 无法覆盖所有的情况,我们可以使用 else if 来进行多重条件链式判断。程序会从上往下依次检查,一旦遇到满足条件的区块,就会立刻执行它并跳出整个判断链。

1
2
3
4
5
6
7
8
9
10
11
12
let currentLikes = 30
let isNewUser = true

if currentLikes > 50 {
print("非常受欢迎的帖子")
} else if currentLikes > 20 {
print("比较受欢迎的帖子")
} else if isNewUser {
print("虽然点赞不多,但是新用户的帖子")
} else {
print("普通的帖子")
}

Swift Basics #5

1. 基础函数的定义与调用

使用 func 关键字可以定义一个函数。函数内部的代码(大括号 {} 里的内容)只有在函数被明确调用时才会执行。函数内部定义的变量是私有的(局部变量),无法被函数外部直接访问,这有助于保护数据安全。

1
2
3
4
5
6
7
8
// 定义一个基础函数
func myFirstFunction() {
let myName = "Nick" // 这是一个局部常量,外部无法访问
print("第一个函数被执行了,名字是:\(myName)")
}

// 在其他地方调用该函数
myFirstFunction()

2. 函数的返回值

有时候我们需要函数执行完某个逻辑后,把结果交还给我们。这时可以在函数声明的括号后面使用箭头 ->,并指定返回数据的类型(比如 StringBool)。请注意,一旦指明了返回类型,函数内部就必须通过 return 关键字返回对应类型的值,否则编译器会报错。

1
2
3
4
5
6
7
8
9
// 定义一个返回 String 类型的函数
func getUserName() -> String {
let username = "Nick"
return username // 将结果抛出函数外
}

// 调用函数,并将返回值赋给一个常量
let name = getUserName()
print(name) // 输出: Nick

3. 向函数传递参数

为了让函数更灵活,我们可以在调用函数时向其“投递”数据。这些数据被称为参数。我们需要在函数声明的括号 () 内定义参数的名称和类型。

1
2
3
4
5
6
7
8
9
10
11
12
// 定义一个需要接收两个 Bool 类型参数的函数
func checkUserStatus(didCompleteOnboarding: Bool, profileIsCreated: Bool) -> Bool {
if didCompleteOnboarding && profileIsCreated {
return true
} else {
return false
}
}

// 调用函数时,必须传入对应类型的数据
let status = checkUserStatus(didCompleteOnboarding: true, profileIsCreated: false)
print(status) // 输出: false

4. 控制流:If 与 Guard 语句

在函数内部控制逻辑走向时,ifguard 是最常用的两种方式:

  • if 语句:如果是,则进入大括号内部执行代码。
  • guard 语句:用于“提前拦截”。确保某件事为真,如果为,则进入 else 大括号内执行代码并立刻 return(退出整个函数),不再执行后续逻辑。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
func checkTitle(title: String) -> Bool {
// 使用 guard 确保 title 必须是 "Avengers"
guard title == "Avengers" else {
// 如果不是 "Avengers",立刻退出函数并返回 false
print("不是漫威电影")
return false
}

// 如果上面的 guard 验证通过了,才会执行到这里
print("是漫威电影")
return true
}

let isMarvel = checkTitle(title: "Batman")

5. 计算属性

如果在某个逻辑块中,我们不需要从外部传入任何参数就能得到一个结果,我们通常可以使用计算属性(Calculated Variables)来替代没有参数的函数。它的写法就像声明一个普通的变量,但在后面跟上一对大括号 {},每次调用这个变量时,大括号里的代码都会被重新执行一遍。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let numberOne = 5
let numberTwo = 8

// 这是一个没有参数传入的函数
func calculateNumbers() -> Int {
return numberOne + numberTwo
}

// 这是一个计算属性,它在功能上几乎等同于上面的函数
var calculatedNumber: Int {
return numberOne + numberTwo
}

// 调用对比
let resultFromFunction = calculateNumbers()
let resultFromVariable = calculatedNumber

Swift Basics #6

1. 什么是可选类型

在 Swift 中,常规的数据类型(如 BoolString)必须始终有一个确定的值。而可选类型代表这个变量“可能有一个具体的值,也可能什么都没有(nil)”。 在数据类型后面加上一个问号 ? 即可将其声明为可选类型。

1
2
3
4
5
6
7
// 常规布尔值,只能是 true 或 false
var normalBool: Bool = false

// 可选布尔值,可以是 true、false,或者是 nil(空值)
var optionalBool: Bool? = nil
optionalBool = true
optionalBool = nil

2. 空值合并运算符提供默认值

当我们试图把一个“可选类型”赋值给一个“非可选类型”时,编译器会报错。最简单的解决办法是使用空值合并运算符 ??。它的含义是:“如果左边有值,就使用左边的值;如果是 nil,就使用右边提供的默认值。”

1
2
3
4
5
6
7
var optionalName: String? = nil

// 如果 optionalName 是 nil,则使用默认值 "Unknown User"
// 此时 nonOptionalName 的类型被安全地推断为常规的 String
let nonOptionalName: String = optionalName ?? "Unknown User"

print(nonOptionalName) // 输出: Unknown User

3. 使用 if let 安全解包

在函数中,我们往往需要确认某个数据真的存在后才执行对应的逻辑。if let 是一种安全的解包方式:“如果这个可选类型有值,就把这个值赋给一个全新的局部常量,并进入大括号内执行代码。”

1
2
3
4
5
6
7
8
9
10
func printUserStatus(userIsPremium: Bool?) {
// 如果 userIsPremium 不是 nil,就会创建一个名为 isPremium 的非可选局部常量
if let isPremium = userIsPremium {
// 在这层大括号内,isPremium 是绝对安全的常规 Bool
print("用户的会员状态是:\(isPremium)")
} else {
// 如果是 nil,则执行 else 里面的逻辑
print("未获取到用户的会员状态")
}
}

(注:在较新的 Swift 版本中,如果变量名相同,你可以简写为 if let userIsPremium { ... })

4. 使用 guard let 提前拦截解包

guard letif let 的作用类似,但逻辑走向相反。它的核心思想是:“确保这个可选类型有值,否则立刻退出(return)当前函数。” 这种方式可以避免代码出现层层嵌套(“死亡金字塔”),让主要逻辑处于函数的最外层,更加清晰明了。

1
2
3
4
5
6
7
8
9
10
11
func setupUserProfile(userName: String?) {
// 确保 userName 有值,否则直接退出当前函数
guard let validName = userName else {
print("用户名为空,无法设置主页,直接退出。")
return
}

// 如果没有被 return 拦截,说明解包成功
// 在这行及之后的整个函数作用域内,validName 都是安全的非可选 String
print("开始为 \(validName) 设置主页...")
}

5. 可选链的使用

有时我们需要访问可选类型内部的某个属性或方法。为了避免繁琐的 if let 嵌套,Swift 提供了可选链操作符 ?.。它的逻辑是:“如果该对象不是 nil,就继续往后执行/获取属性;如果该对象是 nil,立刻停止,并返回一个 nil。”

1
2
3
4
5
6
7
8
var optionalUsername: String? = "Nick"

// 如果 optionalUsername 有值,才去计算 count;如果为 nil,则 characterCount 也为 nil
// 因此 characterCount 的类型是可选整数 Int?
let characterCount: Int? = optionalUsername?.count

// 你还可以结合 ?? 提供默认值,在一行代码内完成安全处理
let safeCount: Int = optionalUsername?.count ?? 0

6. 强制解包的危险性

在某些特殊情况下,开发者可以使用感叹号 ! 对可选类型进行强制解包。这相当于告诉编译器:“我百分之百确定它里面有值,不用管安全机制了”。 强烈警告: 如果你进行了强制解包,但该变量在运行时的实际值却是 nil你的 App 将会直接崩溃(Crash)。在日常开发与面试中,除非极特殊情况,永远不要轻易使用强制解包。

1
2
3
4
5
var optionalUsername: String? = nil 

// ❌ 极度危险的写法:显式强制解包
// 因为当前值为 nil,这行代码运行到这里会导致 App 直接崩溃!
// let characterCount = optionalUsername!.count

Swift Basics #7

1. 创建元组并从函数中返回

如果我们想让函数返回多个值,可以在函数的返回值类型声明中使用圆括号 () 将多个类型包裹起来。在函数内部 return 时,同样使用圆括号将对应的数据打包返回。

1
2
3
4
5
6
7
8
9
10
var username: String = "Hello"
var userIsPremium: Bool = false

// 定义一个返回元组的函数,该元组包含一个 String 和一个 Bool
func getUserInfo() -> (String, Bool) {
return (username, userIsPremium)
}

// 接收返回的元组数据
let userData = getUserInfo()

2. 通过索引访问元组的数据

当我们获取到一个元组后,如果元组内的元素没有具体的名字,Swift 会自动为它们分配基于零的索引(类似于数组)。我们可以使用 . 加上数字索引(如 .0, .1)来提取里面具体的数据。

1
2
3
4
5
6
7
let infoOne = getUserInfo()

// 使用 .0 访问元组中的第一个元素(String)
let nameOne: String = infoOne.0

// 使用 .1 访问元组中的第二个元素(Bool)
let isPremiumOne: Bool = infoOne.1

3. 为元组的参数命名

使用数字索引(.0, .1)在代码量变大时会变得非常难以阅读,因为你很难记住 0 代表什么。为了提升代码可读性,我们可以在声明元组类型时,为每一个元素指定一个明确的名称。

1
2
3
4
5
6
7
8
9
10
// 在返回值声明中,为元组的每一个元素命名
func getUserInfoWithName() -> (name: String, isPremium: Bool) {
return (username, userIsPremium)
}

let infoTwo = getUserInfoWithName()

// 现在可以直接使用具体的名称来访问数据,而不需要使用 .0 或 .1
let nameTwo = infoTwo.name
let isPremiumTwo = infoTwo.isPremium

4. 将元组作为参数传递给其他函数

元组不仅可以作为函数的返回值,它本身也是一种数据类型。这意味着我们可以将整个元组作为一个单独的参数,传递给其他的函数。这在需要同时传递一组相关联的数据时非常有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 定义一个包含三个数据的元组返回函数
func getFullUserInfo() -> (name: String, isPremium: Bool, isNew: Bool) {
return ("Nick", true, false)
}

// 定义一个接收特定元组类型作为参数的函数
func doSomethingWithUserInfo(info: (name: String, isPremium: Bool, isNew: Bool)) {
print("User Name: \(info.name)")
print("Is Premium: \(info.isPremium)")
}

// 获取元组
let fullInfo = getFullUserInfo()

// 将整个元组直接作为参数传递
doSomethingWithUserInfo(info: fullInfo)

Swift Basics #8

1. 对象的生命周期与内存分配

当我们创建一个对象时,实际上是在调用它的“初始化器(Init)”,这会在内存中为其分配空间(Allocate)。当我们在应用中不再需要这个对象时,系统会调用“反初始化器(Deinit)”,将其从内存中释放(Deallocate)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class AppScreen {
// 1. 创建对象时触发,分配内存
init() {
print("屏幕已创建,分配内存")
}

// 2. 销毁对象时触发,释放内存
deinit {
print("屏幕已销毁,释放内存")
}
}

// 模拟创建对象
var screen: AppScreen? = AppScreen()
// 模拟销毁对象
screen = nil

2. 自动引用计数

Swift 使用一种名为 自动引用计数 (ARC, Automatic Reference Counting) 的机制来管理内存。你可以把它理解为一个计数器:每当有一个新的类对象进入内存,计数器加 1;销毁一个对象,计数器减 1。内存中驻留的对象越多,App 运行越慢。因此,优秀的原则是:仅在需要时创建对象,一旦不需要立刻将其销毁。

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

// 此时 ARC 计数为 1
var user1: User? = User(name: "Nick")

// 此时 ARC 计数为 2
var user2: User? = User(name: "Tom")

// 销毁 user1,ARC 计数降为 1
user1 = nil

3. 内存的两种类型:栈与堆

iOS 设备是一个多线程(Multi-threaded)环境。你可以把线程想象成一个个独立的引擎。

  • 栈 (Stack): 每个引擎(线程)都有自己独立的栈。栈速度非常快,内存占用小。大部分基础数据(如 String、Bool、Int、Struct、Enum)都存储在栈中。它们不计入 ARC。
  • 堆 (Heap): 所有的引擎(线程)共享同一个堆。因为需要跨线程同步,堆的速度相对较慢,内存占用大。类(Class)和函数都存储在堆中。只有堆中的对象才会计入 ARC。
1
2
3
4
5
6
7
8
9
// 存储在【栈】中的类型,速度极快
struct UserProfile {
var name: String = "Nick"
}

// 存储在【堆】中的类型,由所有线程共享,需要 ARC 管理
class DatabaseManager {
var isConnected: Bool = true
}

4. 值类型与引用类型的区别

这也是结构体(Struct)和类(Class)最本质的区别:

  • 值类型 (Value Types): 当你修改栈中的数据(如 Struct)时,系统实际上是复制(Copy) 了一份旧数据,粘贴(Paste) 出来一份新副本,并赋上新值。
  • 引用类型 (Reference Types): 堆中的数据(如 Class)是通过“指针”引用的。当你修改它时,你是在直接修改内存中那份唯一的数据本身,而不是创建一个新副本。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct StructData { var count = 0 }
class ClassData { var count = 0 }

// 值类型演示(复制副本)
var dataA = StructData()
var dataB = dataA
dataB.count = 10
print(dataA.count) // 输出: 0 (A 保持不变,因为 B 是一个全新的副本)

// 引用类型演示(修改本体)
var objectA = ClassData()
var objectB = objectA
objectB.count = 10
print(objectA.count) // 输出: 10 (A 也变成了 10,因为 A 和 B 指向内存中的同一个本体)

5. 结构体与类的使用场景

在实际开发中,我们应该如何选择呢?视频中给出了一个生动的比喻:

  • 类就像“教室”: 教室本身不怎么移动,它很稳定,我们在教室内部执行各种动作(如管理数据、网络请求)。对应的开发概念是:Manager、ViewModel、DataService 等核心组件。
  • 结构体就像“试卷”: 试卷被老师复印了很多份,在学生之间传来传去。对应的开发概念是:Data Model(如单纯的用户信息、文章信息)。它们只是一小块数据,在 App 的各个屏幕间被频繁传递。
1
2
3
4
5
6
7
8
9
10
11
12
// 这是一个单纯的数据模型,像“试卷”一样被传来传去,使用 struct
struct QuizModel {
var title: String
var score: Int
}

// 这是一个管理者,像“教室”一样稳定存在,内部执行逻辑,使用 class
class QuizManager {
func startQuiz(quiz: QuizModel) {
print("开始测验:\(quiz.title)")
}
}

Swift Basics #9

1. 隐式初始化器

在 Swift 中,当你创建一个结构体并定义了若干属性后,你不需要手动编写 init(初始化)函数。Swift 编译器会在背后自动为你生成一个包含所有属性的隐式初始化器。

1
2
3
4
5
6
7
struct Quiz {
let title: String
let dateCreated: Date
}

// Swift 自动生成了需要传入 title 和 dateCreated 的初始化器
let myQuiz = Quiz(title: "Swift 基础测试", dateCreated: .now)

2. 自定义初始化器与默认值

虽然有隐式初始化器,但如果你想为某些属性提供默认值,你可以手动编写 init 函数。这样在创建对象时,拥有默认值的参数就可以被省略。

1
2
3
4
5
6
7
8
9
10
11
12
13
struct CustomQuiz {
let title: String
let dateCreated: Date

// 自定义初始化器,为 dateCreated 提供默认值 .now(当前时间)
init(title: String, dateCreated: Date = .now) {
self.title = title
self.dateCreated = dateCreated
}
}

// 创建时,只需传入 title,dateCreated 会自动使用默认值
let quizOne = CustomQuiz(title: "初级测试")

3. 不可变结构体与“修改”方式

如果结构体内部的所有属性都是使用 let 声明的常量,那么这个结构体就是不可变的(Immutable)。这是非常推荐的编程习惯。想要“修改”这种结构体的数据,标准做法是在结构体内部写一个方法,返回一个包含了新数据和旧数据拷贝的全新结构体实例

1
2
3
4
5
6
7
8
9
10
11
12
13
struct ImmutableUser {
let name: String
let isPremium: Bool

// 返回一个全新的 ImmutableUser 对象
func markAsPremium() -> ImmutableUser {
return ImmutableUser(name: self.name, isPremium: true)
}
}

var user1 = ImmutableUser(name: "Nick", isPremium: false)
// 通过覆盖旧变量来完成“更新”
user1 = user1.markAsPremium()

4. 可变结构体与 Mutating 关键字

如果结构体内部使用了 var 声明变量,代表它是可变的。但是,由于结构体是值类型,Swift 默认不允许你在结构体自己的方法中直接修改自己的属性。为了打破这个限制,你必须在方法前面加上 mutating 关键字,明确告诉编译器:“这个方法将会改变结构体自身的数据”。

1
2
3
4
5
6
7
8
9
10
11
12
struct MutableUser {
let name: String
var isPremium: Bool // 使用 var 声明

// 必须添加 mutating 关键字才能修改内部的 var 属性
mutating func updatePremiumStatus(newValue: Bool) {
self.isPremium = newValue
}
}

var user2 = MutableUser(name: "Nick", isPremium: false)
user2.updatePremiumStatus(newValue: true) // 直接在原对象上修改

5. 私有设置器控制访问权限

如果我们把属性公开为 var,外部的任何代码都可以随意篡改它(例如 user2.isPremium = true),这会导致数据来源混乱。为了保证安全,我们可以使用 private(set)。它的作用是:外部代码可以读取这个值,但只能通过结构体内部提供的 mutating 方法来修改它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct SecureUser {
let name: String
// 外部可以读取,但无法直接赋值修改
private(set) var isPremium: Bool

mutating func upgradeUser() {
self.isPremium = true
}
}

var user3 = SecureUser(name: "Nick", isPremium: false)

// let status = user3.isPremium // ✅ 允许读取
// user3.isPremium = true // ❌ 编译器报错:外部不允许修改
user3.upgradeUser() // ✅ 只能通过内部方法修改

Swift Basics #10

1. 枚举的基础概念与语法

使用 enum 关键字可以定义一个枚举。在枚举内部,使用 case 关键字来列出所有可能的选项。这就相当于你创造了一种新的数据类型,它的值只能是你列出的这几个选项之一。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 定义一个名为 OperatingSystem (操作系统) 的枚举类型
enum OperatingSystem {
case iOS
case android
case windows
case macOS
}

// 声明变量并赋值为枚举中的某个选项
var mySystem: OperatingSystem = OperatingSystem.iOS

// 当编译器知道变量的类型时,你可以省略枚举名,直接使用点语法
mySystem = .macOS

2. 为什么需要枚举

在没有枚举时,开发者经常使用纯字符串(String)来表示状态或选项。这种做法极其危险,因为开发者可能会拼写错误,或者传入未预期的值,而编译器无法提前发现这种错误。枚举通过“强制选项”完美解决了这个问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ❌ 危险的做法:使用纯字符串
struct PhoneString {
let systemName: String
}
// 极易拼写错误(比如大小写写错),但代码依然能运行,最终导致 Bug
let wrongPhone = PhoneString(systemName: "ios")

// ✅ 安全的做法:使用枚举
struct PhoneEnum {
let system: OperatingSystem
}
// 强类型检查:当你输入一个点(.)时,Xcode 会自动提示可用选项
// 如果你输入了不存在的选项,代码将直接报错并拒绝编译
let safePhone = PhoneEnum(system: .iOS)

3. 枚举与分支语句的结合

枚举最完美的搭档是 switch 语句。由于枚举的选项是有限且已知的,当你使用 switch 检查枚举时,Swift 编译器会强制你处理所有可能的 case。这种“穷举检查”可以确保你不会遗漏任何一种情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
let currentSystem = OperatingSystem.macOS

// 编译器会强制你写出所有的 case,否则报错
switch currentSystem {
case .iOS:
print("这是一台苹果移动设备")
case .android:
print("这是一台安卓设备")
case .windows:
print("这是一台 Windows 电脑")
case .macOS:
print("这是一台 Mac 电脑")
}

4. 枚举的原始值

你可以为枚举的每一个 case 绑定一个底层的基础值(称为原始值),通常是 StringInt。通过在定义枚举时指定类型,并在选项后赋值,你就可以随时通过 .rawValue 属性来提取这个底层数据。

1
2
3
4
5
6
7
8
9
10
11
12
// 声明该枚举的原始值类型为 String
enum Theme: String {
case light = "明亮模式"
case dark = "暗黑模式"
case system = "跟随系统设置"
}

let currentTheme = Theme.dark

// 获取枚举的原始值
print(currentTheme.rawValue)
// 最终输出内容为:暗黑模式

5. 通过原始值初始化枚举

除了提取原始值,如果你手头只有一个普通的数据(比如从网络请求拿到的字符串),你也可以尝试用它来“转换”生成一个枚举对象。因为传入的值可能不匹配任何选项,所以这个初始化方法返回的是一个可选类型(Optional)

1
2
3
4
5
6
7
8
let databaseString = "明亮模式"

// 尝试用字符串生成一个 Theme 枚举,因为可能失败,所以生成的是 Theme?
if let matchedTheme = Theme(rawValue: databaseString) {
print("成功转换为枚举选项:\(matchedTheme)")
} else {
print("无效的字符串,无法转换为枚举")
}

Swift Basics #11

1. 类的定义与手动初始化

类的基本声明语法与结构体非常相似,使用 class 关键字。但最显著的编码区别在于:类没有隐式初始化器。如果类内部的属性没有默认值,你必须手动编写 init 函数来完成初始化操作,否则编译器会直接报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
class ScreenViewModel {
let title: String
var showButton: Bool

// 必须手动编写 init 初始化器
init(title: String, showButton: Bool) {
self.title = title
self.showButton = showButton
}
}

// 实例化类对象
let myViewModel = ScreenViewModel(title: "首页", showButton: true)

2. 修改内部数据无需关键字

在结构体中,如果要在自身的方法内修改属性,必须在方法前加上 mutating 关键字。但是,类天生就是在原地修改内存数据的,因此在类内部修改自身属性时,完全不需要任何特殊的关键字。

1
2
3
4
5
6
7
8
9
10
11
12
class UserDataManager {
var isPremium: Bool = false

// 普通函数即可直接修改内部的 var 属性,不需要 mutating
func upgradeToPremium() {
self.isPremium = true
print("用户已升级为高级会员")
}
}

let manager = UserDataManager()
manager.upgradeToPremium()

3. 引用类型的本质与数据共享

类是引用类型,这意味着它在传递时传递的是“内存指针”。如果你把一个类实例赋值给另一个变量,你其实并没有复制数据,而是让两个变量指向了内存中的同一个对象。改动其中一个,另一个也会同步改变。

1
2
3
4
5
6
7
8
9
10
11
class Car {
var speed: Int = 0
}

let carA = Car()
let carB = carA // carB 和 carA 指向内存中的同一辆车

carB.speed = 100

// 因为指向同一个本体,所以 carA 的速度也变成了 100
print(carA.speed) // 输出: 100

4. 常量实例与可变属性

这是类与结构体在行为上极其反直觉的一点!如果你使用 let(常量)来声明一个结构体实例,那么它内部的所有数据都将被完全锁死,无法修改。 但是,如果你使用 let 声明一个类实例,只要类内部的属性是用 var 定义的,你依然可以自由修改这些属性的值。这是因为 let 仅仅锁死了“这个常量不能再指向另一个类实例”,但并没有锁死该内存地址里存储的数据本身。

1
2
3
4
5
6
7
8
9
10
11
12
class NetworkService {
var isConnected: Bool = false
}

// 使用 let 声明为一个常量
let myService = NetworkService()

// ✅ 即使 myService 是常量,依然可以修改内部的 var 属性
myService.isConnected = true

// ❌ 只有试图让 myService 指向一个全新的实例时,才会报错
// myService = NetworkService()

Swift Basics #12

1. 默认权限 (Public / Internal)

当你声明一个变量或函数,并且不在前面加任何权限修饰符时,在绝大多数日常开发中,你可以把它视作是公开的(Public)。这意味着在类的外部,你可以随意读取(Get)并且随意修改(Set)这个变量。

注意:虽然这最方便,但在优秀的架构设计中,这往往被认为是“过于公开”的,不够安全。

1
2
3
4
5
6
7
8
9
10
11
12
class MovieManager {
// 没有任何修饰符,默认为公开
var movieOne: String = "Avatar"
}

let manager = MovieManager()

// ✅ 外部可以读取 (Get)
let title = manager.movieOne

// ✅ 外部可以随意修改 (Set)
manager.movieOne = "Titanic"

2. 私有权限 (Private)

在声明前面加上 private 关键字,意味着这个属性或方法只能在这个类(或结构体)的大括号内部被访问。外部代码既不能读取它,也不能修改它。这通常用于隐藏类内部的计算细节或敏感数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class SecureManager {
// 标记为私有
private var secretMovie: String = "Avengers"

// 可以在类的内部随意使用它
func printMovie() {
print(secretMovie)
}
}

let secureManager = SecureManager()

// ❌ 外部无法读取 (Get) 报错
// let name = secureManager.secretMovie

// ❌ 外部无法修改 (Set) 报错
// secureManager.secretMovie = "Batman"

3. 私有设置器 (Private Set)

这是在实际应用架构(如 MVVM)中最常用、也是最被推荐的最佳实践。使用 private(set) 意味着:对外部开放读取权限(Public Get),但修改权限依然是私有的(Private Set)。 外部如果想要修改这个值,只能通过调用类提供的方法来实现。这样类就能完全掌控数据被修改的规则和时机。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class AdvancedManager {
// 外部可读,内部可写
private(set) var movieThree: String = "Inception"

// 类提供一个公开方法,允许外部通过特定渠道修改数据
func updateMovie(newTitle: String) {
self.movieThree = newTitle
}
}

let advancedManager = AdvancedManager()

// ✅ 外部可以自由读取 (Get)
let myTitle = advancedManager.movieThree

// ❌ 外部直接修改 (Set) 会报错!
// advancedManager.movieThree = "Interstellar"

// ✅ 必须通过类提供的方法来更新数据
advancedManager.updateMovie(newTitle: "Interstellar")

Swift Basics #13

1. 数组的定义与初始化

数组是一种有序的列表。在 Swift 中,我们使用方括号 [] 来包裹数组的内容,并通过 [数据类型] 来声明数组的类型。数组中存放的所有元素都必须是同一种类型。

1
2
3
4
5
6
7
8
// 声明一个明确类型为 String 数组的变量
var fruits: [String] = ["Apple", "Orange"]

// 也可以使用泛型写法(效果相同,在高级开发中常见)
var fruitsTwo: Array<String> = ["Apple", "Orange"]

// 数组同样支持布尔值或数字
var myBools: [Bool] = [true, false, true]

2. 统计元素个数与获取首尾元素

当你拿到一个数组时,你可以随时查看它里面包含多少个元素。同时,你也可以快速获取排在第一位或最后一位的元素。由于数组有可能是空的(里面没有任何数据),所以获取首尾元素返回的其实是可选类型(Optional)

1
2
3
4
5
6
7
8
9
10
11
12
13
let fruitsArray = ["Apple", "Orange", "Banana"]

// 获取数组中元素的总个数,返回结果为 3
let count = fruitsArray.count

// 获取首尾元素(注意:返回值是 String? 类型)
let firstItem = fruitsArray.first
let lastItem = fruitsArray.last

// 使用 if let 安全解包第一个元素
if let safeFirst = fruitsArray.first {
print("第一个水果是:\(safeFirst)")
}

3. 通过索引访问数据与“越界崩溃”

数组的计算(Count)是从 1 开始的,但是数组的索引(Index)是从 0 开始的。也就是说,第一个元素的索引是 0,第二个是 1,以此类推。 严重警告: 如果你尝试通过索引直接访问一个不存在的位置(例如数组只有 3 个元素,你却去拿索引为 10 的元素),你的 App 会立刻崩溃(Crash)

1
2
3
4
5
6
7
8
9
10
11
12
let fruitsArray = ["Apple", "Orange", "Banana"]
// 索引对应关系: 0 1 2

// 通过下标直接访问元素
let secondFruit = fruitsArray[1] // 返回 "Orange"

// ✅ 安全访问的做法:先确认该索引是否存在于数组内
if fruitsArray.indices.contains(5) {
let mysteryFruit = fruitsArray[5]
} else {
print("索引越界,拒绝访问以防止崩溃")
}

4. 动态修改数组元素

只要数组是被声明为变量(var)的,我们就可以在代码运行期间随意地在任意位置添加或移除数据。修改数组实际上也是在变相地“变异(Mutating)”原有的结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
var myFruits = ["Apple", "Orange"]

// 1. 追加 (Append):默认将元素直接添加到数组的最末尾
myFruits.append("Banana")

// 2. 插入 (Insert):将元素强行塞进指定的索引位置,原位置及后面的元素整体后移
myFruits.insert("Watermelon", at: 1)

// 3. 移除 (Remove):精准删除指定索引位置的元素
myFruits.remove(at: 0)

// 4. 清空数组
myFruits.removeAll()

5. 存放自定义对象

数组不仅可以存放系统自带的字符串或数字,它也可以用来装载你自己定义的结构体(Struct)或类(Class)。这是构建真实 App 最常见的数据流转方式。

1
2
3
4
5
6
7
8
9
10
struct ProductModel {
let title: String
let price: Int
}

// 创建一个专门存放 ProductModel 结构体的数组
var myProducts: [ProductModel] = [
ProductModel(title: "iPhone", price: 999),
ProductModel(title: "MacBook", price: 1999)
]

6. 集合与数组的核心区别

集合(Sets) 的语法看起来和数组很像,但它有两个决定性的区别:

  1. 元素必须唯一: 集合里绝对不允许出现两个一模一样的数据(会自动去重)。
  2. 完全无序: 集合没有“第一项”或“第二项”的概念,每次打印出来的顺序可能都不一样,因此你无法通过像 [0] 这样的索引去访问它。
1
2
3
4
5
6
7
// 数组:允许重复,严格保持存入时的顺序
let fruitsArray: [String] = ["Apple", "Orange", "Apple"]
print(fruitsArray) // 输出: ["Apple", "Orange", "Apple"]

// 集合:自动剔除重复项,且打乱顺序
let fruitsSet: Set<String> = ["Apple", "Orange", "Apple"]
print(fruitsSet) // 输出: ["Orange", "Apple"] 或 ["Apple", "Orange"]

Swift Basics #14

1. 字典的定义与初始化

在声明字典时,我们需要在方括号内同时指定“键(Key)”的类型和“值(Value)”的类型,中间用冒号 : 隔开。字典中的“键”必须是唯一的,绝对不能重复。

1
2
3
4
5
6
7
8
9
10
11
// 声明一个键为 String 类型,值为 Bool 类型的字典
var myFirstDictionary: [String: Bool] = [
"Apple": true,
"Orange": false
]

// 键和值可以是任意类型(比如键是 Int,值是 String)
var anotherDictionary: [Int: String] = [
0: "Apple",
176: "Banana"
]

2. 安全的数据访问机制(不会越界崩溃)

在上一节课中提到,如果访问了数组中不存在的索引,App 会直接崩溃。但字典非常安全:当你尝试通过一个 Key 去获取数据时,如果这个 Key 不存在,它只会平静地返回 nil。 正因如此,字典通过 Key 提取出来的值永远是可选类型(Optional)

1
2
3
4
5
6
7
8
9
10
11
12
let fruitsDict: [String: Bool] = ["Apple": true]

// 访问存在的键,返回 Optional(true)
let appleValue = fruitsDict["Apple"]

// 访问不存在的键,不会崩溃,而是返回 nil
let bananaValue = fruitsDict["Banana"]

// 结合 if let 进行安全解包使用
if let isApple = fruitsDict["Apple"] {
print("Apple 的状态是:\(isApple)")
}

3. 动态添加与更新数据

因为字典是无序的,所以它没有 append(追加到末尾)或 insert(插入到指定位置)的概念。要想添加或更新数据,你只需要直接通过对应的 Key 进行赋值即可。如果 Key 已经存在,旧数据会被覆盖;如果 Key 不存在,就会新增一组键值对。

1
2
3
4
5
6
7
8
9
10
var myDict: [String: String] = [
"ABC": "Apple",
"DEF": "Banana"
]

// 添加新数据(因为 XYZ 之前不存在)
myDict["XYZ"] = "Mango"

// 更新旧数据(因为 ABC 已经存在)
myDict["ABC"] = "Red Apple"

4. 移除字典中的数据

除了添加数据,我们也可以随时从字典中精准剔除不需要的键值对。使用 removeValue(forKey:) 方法即可完成操作。

1
2
3
4
5
6
7
var myDict: [String: String] = [
"ABC": "Apple",
"DEF": "Banana"
]

// 移除键为 "DEF" 的整组数据
myDict.removeValue(forKey: "DEF")

5. 结合自定义模型的高级用法

在真实的项目中,我们经常从后端服务器获取包含唯一 ID 的数据模型。将这些唯一 ID 作为“键”,将数据模型本身作为“值”存入字典,可以实现极速的数据查询。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct PostModel {
let id: String
let title: String
let likeCount: Int
}

// 创建一个以帖子 ID 为键,以 PostModel 为值的字典
var postDictionary: [String: PostModel] = [
"abc123": PostModel(id: "abc123", title: "第一篇帖子", likeCount: 5),
"def456": PostModel(id: "def456", title: "第二篇帖子", likeCount: 10)
]

// 当你需要查找某篇帖子时,只需知道 ID 就能瞬间获取,无需遍历整个列表
let specificPost = postDictionary["def456"]

6. 数组与字典的选择场景

  • 使用数组(Arrays): 当数据的顺序至关重要时(比如要在屏幕的列表中从上到下按序展示信息),必须使用数组。

  • 使用字典(Dictionaries): 当数据顺序无所谓,但你需要通过唯一标识符(ID)进行极速查找,且不想担心越界崩溃问题时,字典是最佳选择。

Swift Basics #15

1. 使用数字范围进行基础循环

你可以设定一个数字范围,让一段代码精准执行指定的次数。在 Swift 中,通常使用半开区间 0..<上限 来进行循环,这非常符合编程中从 0 开始计数的习惯。

1
2
3
4
// 循环 50 次,数字从 0 一直变化到 49
for x in 0..<50 {
print("当前正在执行第 \(x) 次循环")
}

2. 直接遍历数组中的元素

当你已经有一个数组时,不需要关心它有多长,直接使用 for item in array 的语法,系统会自动把数组里的每一项依次提取出来供你使用。

1
2
3
4
5
6
let myStringArray: [String] = ["Alpha", "Beta", "Gamma"]

// loopItem 代表当前正在遍历的那个具体的字符串
for loopItem in myStringArray {
print("当前读取到的字母是:\(loopItem)")
}

3. 在循环中筛选与重组数据

我们经常在循环体内部嵌套 if 逻辑判断语句,用来检查当前元素是否符合特定条件。如果符合,我们可以把它提取出来存入另一个全新的数组中,从而实现数据的“手动筛选”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct Lesson {
let title: String
let isFavorite: Bool
}

let allLessons = [
Lesson(title: "第一课", isFavorite: false),
Lesson(title: "第二课", isFavorite: true),
Lesson(title: "第三课", isFavorite: true)
]

var favoriteLessons: [Lesson] = []

// 遍历所有课程,将标记为喜欢的课程单独挑出来存入新数组
for currentLesson in allLessons {
if currentLesson.isFavorite {
favoriteLessons.append(currentLesson)
}
}

4. 同时获取索引与元素的枚举循环

普通的遍历只能拿到元素本身。但有时候我们在循环时,既需要拿到“元素”,又需要知道它是数组里的“第几个(索引)”。这时候可以使用 .enumerated() 方法,它会同时返回索引和对应的元素。

1
2
3
4
5
6
let animals = ["Dog", "Cat", "Bird"]

// 同时提取出 index(索引)和 animal(具体元素)
for (index, animal) in animals.enumerated() {
print("索引编号 \(index) 对应的动物是 \(animal)")
}

5. 循环的流程控制指令

在循环过程中,我们可以根据特定条件随时干预循环的走向,最常用的两个指令是 breakcontinue

  • 终止指令 (break): 直接砸碎并跳出整个循环。一旦触发,循环彻底结束,后面的所有元素都不再遍历。
  • 跳过指令 (continue): 仅仅放弃当前这一次遍历中剩下的代码,直接跳到下一个元素继续执行后续的循环。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let numbers = [0, 1, 2, 3, 4]

for number in numbers {
if number == 1 {
// 如果当前数字是 1,直接跳过它,去遍历下一个数字(2)
continue
}

if number == 3 {
// 如果当前数字是 3,直接砸碎整个循环,后续的 4 也不再遍历了
break
}

print("正在处理数字:\(number)")
}
// 最终打印结果将只有:0 和 2

Swift Basics #16

1. 使用 Filter 筛选数据

当我们需要根据某个条件,从一个大数组中挑出符合条件的部分元素,并组成一个新的数组时,使用 .filter 是最简单的方法。它会在背后自动遍历数组,你只需要告诉它“什么样的元素可以被保留(返回 true)”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct DatabaseUser {
let name: String
let isPremium: Bool
}

let allUsers = [
DatabaseUser(name: "Nick", isPremium: true),
DatabaseUser(name: "Emily", isPremium: false),
DatabaseUser(name: "Joe", isPremium: true)
]

// 基础写法:遍历每个 user,如果 isPremium 为 true,则保留在数组中
let premiumUsers = allUsers.filter { user in
return user.isPremium
}

// 👑 简写语法:使用 $0 代表当前遍历到的元素
let premiumUsersShorthand = allUsers.filter { $0.isPremium }

2. 使用 Sort 对数据进行排序

如果数组中的元素是自定义的结构体或类,系统并不知道该按什么规则排序(比如是按字母排,还是按价格排?)。此时,我们需要使用 .sorted 方法,并提供一个排序规则:当你拿出任意两个元素(比如 user1user2)进行对比时,谁应该排在前面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct OrderedUser {
let name: String
let order: Int
}

let usersList = [
OrderedUser(name: "Nick", order: 4),
OrderedUser(name: "Emily", order: 1),
OrderedUser(name: "Joe", order: 10)
]

// 基础写法:每次拿出两个 user 进行比较,如果 user1 的 order 更小,就排在前面
let sortedUsers = usersList.sorted { user1, user2 in
return user1.order < user2.order
}

// 👑 简写语法:使用 $0 代表拿出的第一个元素,$1 代表拿出的第二个元素
let sortedUsersShorthand = usersList.sorted { $0.order < $1.order }
// 排序后的结果将会是:Emily(1), Nick(4), Joe(10)

3. 使用 Map 映射转换数据类型

这是开发中最常用的魔法之一!当我们有一个复杂的对象数组(例如包含姓名、年龄、VIP 状态的用户模型数组),但我们仅仅只需要他们的名字,想要得到一个纯字符串数组([String])时,就可以使用 .map。它会遍历原数组,将每一个元素“转换”为你想要的形态,并返回一个全新的数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct UserProfile {
let name: String
let age: Int
}

let profiles = [
UserProfile(name: "Nick", age: 25),
UserProfile(name: "Emily", age: 22)
]

// 基础写法:遍历每个 profile 对象,仅仅将它的 name 属性返回
// 最终将 [UserProfile] 转换为了 [String] 数组
let userNames: [String] = profiles.map { profile in
return profile.name
}

// 👑 简写语法:使用 $0 提取属性
let userNamesShorthand: [String] = profiles.map { $0.name }
// 转换后的结果:["Nick", "Emily"]