Swift 开发教程:入门与介绍
欢迎来到 Swift 编程的奇妙世界!如果你一直梦想着开发 iOS、macOS、watchOS 或 tvOS 应用,或者对构建高性能的服务器端应用或命令行工具感兴趣,那么 Swift 绝对是你的最佳选择。作为 Apple 官方推荐的现代编程语言,Swift 以其安全性、速度和表现力赢得了全球开发者的青睐。
本教程旨在为你提供一个全面而深入的 Swift 入门指南。我们将从 Swift 的基本概念讲起,带你了解这门语言的独特之处,并逐步学习其核心语法和特性。无论你是否有编程经验,这篇文章都将为你铺平通往 Swift 开发的道路。
第一章:初识 Swift – 它是什么?为什么选择它?
1.1 什么是 Swift?
Swift 是一种由 Apple 公司开发的通用、多范式、编译型编程语言。它于 2014 年首次发布,旨在取代 Apple 生态系统中长期使用的 Objective-C 语言。Swift 的设计目标是:
- 安全 (Safe): 语言本身强制执行许多安全检查,帮助开发者避免常见的编程错误,如空指针引用(Swift 中的 Optional 类型是关键特性)、缓冲区溢出等。
- 快速 (Fast): Swift 使用了高性能的 LLVM 编译器,并且在设计时考虑了性能,使其在许多任务上表现出色,甚至可以与 C++ 相媲美。
- 富有表现力 (Expressive): Swift 的语法简洁、易读,使用了现代编程语言的特性,使得代码更加清晰和易于维护。它吸收了 Objective-C、Rust、Haskell、Ruby、Python、C# 等多种语言的优点。
- 开源 (Open Source): Swift 于 2015 年底开源,拥有一个活跃的社区。这使得 Swift 不仅局限于 Apple 平台,还被用于 Linux、Windows 等其他操作系统,甚至用于服务器端开发(如 Vapor 和 Kitura 框架)。
1.2 为什么选择 Swift?
- Apple 生态核心: 如果你的目标是开发 iPhone, iPad, Mac, Apple Watch 或 Apple TV 应用,Swift 是官方和首选的语言。所有的 Apple 平台 API (如 UIKit, SwiftUI, AppKit, Core Data 等) 都对 Swift 提供了优秀的集成和支持。
- 现代语言特性: Swift 抛弃了 Objective-C 中一些老旧或繁琐的特性(如头文件、手动内存管理 – 尽管后来有了 ARC,但 Swift 的内存管理更集成),引入了 Optional、协议扩展、泛型、枚举关联值、模式匹配等现代、强大的特性,使得代码更安全、更简洁。
- 强大的工具链: Xcode 是 Apple 提供的集成开发环境(IDE),为 Swift 开发提供了强大的支持,包括代码编辑、调试、界面设计(Interface Builder 或 SwiftUI Preview)、性能分析等。Swift Playgrounds 则是一个极好的学习和实验工具,可以实时看到代码执行结果。
- 性能优异: 对于许多需要高性能计算的任务,Swift 提供了接近原生代码的速度。
- 不断发展: Swift 语言本身和其生态系统都在快速发展,新版本不断带来改进和新功能。
- 活跃的社区: 开源使得 Swift 拥有一个庞大的开发者社区,你可以轻松找到教程、库、框架以及寻求帮助。
总而言之,学习 Swift 意味着你站在了构建现代、安全、高性能应用的起跑线上,尤其是在 Apple 平台上。
第二章:准备环境 – Xcode 与 Playgrounds
学习 Swift 最好的方式就是动手实践。你需要安装 Apple 提供的开发工具 Xcode。
2.1 安装 Xcode
Xcode 是在 macOS 上进行 Apple 平台开发(包括 Swift 语言开发)的官方 IDE。
- 系统要求: Xcode 只能安装在 macOS 操作系统上。你需要一台 Mac 电脑。请查阅最新版 Xcode 的系统要求,确保你的 macOS 版本兼容。
- 下载与安装:
- 打开 Mac 上的 App Store 应用。
- 搜索 “Xcode”。
- 点击获取并安装。Xcode 的体积较大(通常是几个 GB),下载和安装可能需要一些时间,取决于你的网络速度。
- 启动 Xcode: 安装完成后,在“应用程序”文件夹中找到并打开 Xcode。首次启动可能需要同意许可协议并进行一些初始设置。
2.2 使用 Swift Playgrounds
对于初学者来说,Xcode 内置的 Swift Playgrounds 是一个极好的学习工具。它允许你编写 Swift 代码,并立即看到每一行或每一段代码的执行结果,无需构建完整的应用项目。
- 创建新的 Playground:
- 打开 Xcode。
- 选择 “File” > “New” > “Playground”。
- 选择一个模板(例如 “Blank” 或 “iOS” -> “Blank”)。
- 给你的 Playground 文件命名并选择保存位置。
- 点击 “Create”。
- Playground 界面: Playground 窗口通常分为三个主要区域:
- 代码编辑器: 在这里编写你的 Swift 代码。
- 结果侧边栏: 显示每一行代码执行后的结果或变量的当前值。
- 控制台区域: (有时在底部或通过菜单显示)显示
print()
函数输出的内容或错误信息。
- 运行代码: Playground 通常会在你输入代码时自动编译和执行。你也可以点击编辑器底部的播放按钮来手动执行代码。
我们将主要使用 Playground 来演示 Swift 的基本语法和特性。
第三章:Swift 基础 – 变量、常量、数据类型与运算符
一切编程语言都始于最基本的构建块。在 Swift 中,这些包括如何存储数据(变量和常量)、数据的种类(数据类型)以及如何操作数据(运算符)。
3.1 变量与常量
在 Swift 中,你可以使用 var
关键字声明变量,使用 let
关键字声明常量。常量的值一旦设定后就不能改变,而变量的值可以在程序执行过程中多次修改。
“`swift
// 声明一个变量
var greeting = “Hello, playground” // greeting 的值是 “Hello, playground”
greeting = “Hello, Swift!” // 可以修改 greeting 的值
// 声明一个常量
let pi = 3.14159 // pi 的值是 3.14159
// pi = 3.0 // 这行代码会导致编译错误,因为 pi 是常量,不能修改
“`
最佳实践: 优先使用 let
来声明常量。只有当你明确知道一个值需要改变时,才使用 var
。使用常量可以提高代码的可读性和安全性,防止意外修改。
3.2 类型推断与类型注解
Swift 是一种静态类型语言,这意味着每个变量和常量在编译时就确定了类型。但是,Swift 具有强大的类型推断能力,通常可以根据你赋给变量或常量的值自动推断出其类型,省去了显式声明类型的麻烦。
swift
var name = "Alice" // Swift 推断 name 的类型是 String
var age = 30 // Swift 推断 age 的类型是 Int
let height = 1.75 // Swift 推断 height 的类型是 Double
let isStudent = true // Swift 推断 isStudent 的类型是 Bool
虽然 Swift 可以自动推断类型,但在某些情况下,或者为了代码的清晰度,你可以显式地进行类型注解。类型注解的语法是在变量或常量名后加上冒号 :
,然后是类型名称。
swift
var message: String = "Welcome!"
var count: Int = 100
let price: Double = 9.99
let isCompleted: Bool = false
如果你声明变量或常量时没有赋初始值,则必须进行类型注解:
swift
var futureMessage: String // 正确,没有初始值,但指定了类型
// var anotherCount // 错误,没有初始值,也没有指定类型
3.3 常见数据类型
Swift 提供了多种内置的数据类型来表示不同种类的数据:
- 整型 (Integers): 表示整数,如 10, -5。
Int
: 默认的整型类型,其大小与当前平台的原生字长相同(在 32 位系统上是 Int32,在 64 位系统上是 Int64)。通常情况下,直接使用Int
就足够了。UInt
: 无符号整型,只能表示非负整数。- 特定大小的整型:
Int8
,UInt8
,Int16
,UInt16
,Int32
,UInt32
,Int64
,UInt64
。
- 浮点型 (Floating-Point Numbers): 表示带小数的数字。
Double
: 表示 64 位浮点数,精度更高,是默认的浮点类型。Float
: 表示 32 位浮点数,精度较低。
- 布尔型 (Booleans): 表示真或假。
Bool
: 只有两个值:true
和false
。
- 字符串 (Strings): 表示文本。
String
: 使用双引号" "
括起来的文本序列。
- 字符 (Characters): 表示单个字符。
Character
: 使用双引号" "
括起来的单个字符。
类型安全: Swift 是类型安全的。这意味着如果你尝试将一个类型的值赋给另一个类型的变量或常量(或者进行不允许的类型转换),Swift 会在编译时报错。这有助于防止运行时错误。
“`swift
let integerValue = 10
let doubleValue = 3.14
// let sum = integerValue + doubleValue // 错误:不能直接将 Int 和 Double 相加
// 需要进行类型转换
let sum = Double(integerValue) + doubleValue // 正确:将 Int 转换为 Double 再相加
print(sum) // 输出 13.14
“`
3.4 运算符
运算符用于对值执行操作。Swift 支持常见的运算符:
- 算术运算符:
+
(加),-
(减),*
(乘),/
(除),%
(取模)
swift
let a = 10
let b = 3
print(a + b) // 13
print(a - b) // 7
print(a * b) // 30
print(a / b) // 3 (整型除法)
print(a % b) // 1 - 赋值运算符:
=
(赋值)
swift
var x = 5
x = 10 // 将 10 赋给 x - 复合赋值运算符:
+=
,-=
,*=
,/=
,%=
swift
var count = 5
count += 3 // 等价于 count = count + 3
print(count) // 8 - 比较运算符:
==
(等于),!=
(不等于),>
(大于),<
(小于),>=
(大于等于),<=
(小于等于)。这些运算符返回布尔值 (true
或false
)。
swift
let p = 10
let q = 20
print(p == q) // false
print(p < q) // true - 逻辑运算符:
&&
(逻辑与),||
(逻辑或),!
(逻辑非)
swift
let isHappy = true
let isTired = false
print(isHappy && !isTired) // true (真且非假 -> 真且真)
print(isHappy || isTired) // true (真或假) - 区间运算符:
- 闭区间
a...b
: 包含从 a 到 b 的所有值,包括 a 和 b。 - 半开区间
a..<b
: 包含从 a 到 b 的所有值,不包括 b。
swift
for index in 1...5 { // 包含 1, 2, 3, 4, 5
print("\(index) * 5 = \(index * 5)")
}
for index in 0..<5 { // 包含 0, 1, 2, 3, 4
print(index)
}
- 闭区间
- 空合运算符 (Nil-Coalescing Operator):
a ?? b
。如果可选类型a
包含一个值,则解包a
并返回该值;否则,返回默认值b
。这是处理可选类型的一种简洁方式(我们稍后会详细介绍可选类型)。
第四章:控制流 – 条件判断与循环
控制流语句允许你根据条件执行不同的代码块,或者重复执行某段代码。
4.1 条件语句
-
If 语句:
“`swift
let temperature = 25if temperature < 0 {
print(“天气太冷了!”)
} else if temperature >= 0 && temperature < 15 {
print(“有点冷,注意保暖。”)
} else if temperature >= 15 && temperature < 25 {
print(“天气不错。”)
} else { // 否则 (temperature >= 25)
print(“天气有点热。”)
}
``
if
Swift 的语句不需要在条件周围使用圆括号
(),但花括号
{}` 是必须的。 -
Switch 语句:
switch
语句可以根据一个值匹配多种可能的情况(模式)。它比多个if-else if
语句更适合处理多个分支的情况。
“`swift
let dayOfWeek = “Monday”switch dayOfWeek {
case “Monday”:
print(“新的一周开始了!”)
case “Friday”:
print(“周末快到了!”)
case “Saturday”, “Sunday”: // 可以匹配多个值
print(“是周末!”)
default: // 必须包含 default 情况,除非 switch 覆盖了所有可能的值 (例如,使用 enum)
print(“是工作日。”)
}
``
switch
Swift 的语句是**穷尽的 (exhaustive)**,这意味着它必须覆盖所有可能的情况。如果不能穷尽,就需要提供一个
default分支。与许多其他语言不同,Swift 的
switch在匹配到一个
case并执行完其代码后,不会自动“贯穿 (fallthrough)”到下一个
case。如果你需要这种行为,可以使用
fallthrough` 关键字(但这在实践中不常用)。switch
还可以进行值绑定、区间匹配、元组匹配等更高级的操作,使其非常强大。“`swift
let score = 85switch score {
case 0..<60:
print(“不及格”)
case 60..<80:
print(“及格”)
case 80..<90:
print(“良好”)
case 90…100:
print(“优秀”)
default:
print(“分数无效”)
}let somePoint = (1, 1)
switch somePoint {
case (0, 0):
print(“(somePoint) is at the origin”)
case (, 0): // 匹配 x 轴上的点
print(“(somePoint) is on the x-axis”)
case (0, ): // 匹配 y 轴上的点
print(“(somePoint) is on the y-axis”)
case (-2…2, -2…2): // 匹配在特定矩形区域内的点
print(“(somePoint) is inside the box”)
default:
print(“(somePoint) is outside of the box”)
}
“`
4.2 循环语句
-
For-In 循环: 用于遍历序列,如数组、字典、范围、字符串中的字符等。
“`swift
// 遍历范围
for number in 1…5 {
print(number) // 输出 1, 2, 3, 4, 5
}// 遍历数组 (稍后会详细介绍数组)
let fruits = [“Apple”, “Banana”, “Cherry”]
for fruit in fruits {
print(“我喜欢吃 (fruit)”)
}// 遍历字符串中的字符
let greeting = “Hello”
for character in greeting {
print(character)
}// 如果不需要使用循环中的值,可以使用下划线 _
for _ in 1…3 {
print(“重复三次”)
}
“` -
While 循环: 当条件为真时重复执行代码块。在每次循环开始前检查条件。
swift
var countdown = 5
while countdown > 0 {
print("\(countdown)...")
countdown -= 1
}
print("发射!") -
Repeat-While 循环: 在执行完代码块后检查条件。这保证了循环体至少会执行一次。
“`swift
var i = 0
repeat {
print(“执行一次循环体”)
i += 1
} while i < 0 // 条件为假// 输出: 执行一次循环体
“`
第五章:集合类型 – 数组、字典与集合
Swift 提供了三种主要的集合类型来存储一组值:数组 (Arrays)、字典 (Dictionaries) 和集合 (Sets)。
5.1 数组 (Arrays)
数组是存储相同类型值的有序集合。数组中的每个值都有一个对应的从零开始的索引。
-
声明与初始化:
swift
var shoppingList: [String] = ["牛奶", "面包"] // 声明并初始化,指定类型
var ages = [Int]() // 声明一个空的 Int 数组
var prices = [Double](repeating: 0.0, count: 3) // 创建一个包含 3 个 0.0 的数组
var names = ["Alice", "Bob", "Charlie"] // Swift 推断类型为 [String] -
访问与修改:
“`swift
print(names[0]) // 输出 “Alice” (通过索引访问)
names[1] = “Bobby” // 修改索引为 1 的元素
print(names) // 输出 [“Alice”, “Bobby”, “Charlie”]names.append(“David”) // 在末尾添加元素
names += [“Eve”, “Frank”] // 使用 + 操作符添加多个元素
print(names)names.insert(“Cindy”, at: 2) // 在指定索引插入元素
print(names) // 输出 [“Alice”, “Bobby”, “Cindy”, “Charlie”, “David”, “Eve”, “Frank”]names.remove(at: 0) // 移除指定索引的元素
print(names) // 输出 [“Bobby”, “Cindy”, “Charlie”, “David”, “Eve”, “Frank”]let removedName = names.removeLast() // 移除并返回最后一个元素
print(removedName) // 输出 “Frank”
print(names) // 输出 [“Bobby”, “Cindy”, “Charlie”, “David”, “Eve”]
“` -
遍历:
“`swift
for name in names {
print(name)
}// 同时获取索引和值
for (index, value) in names.enumerated() {
print(“Item (index + 1): (value)”)
}
“` -
常用属性和方法:
swift
print(names.count) // 元素个数
print(names.isEmpty) // 是否为空
print(names.first) // 第一个元素 (可选类型,因为数组可能为空)
print(names.last) // 最后一个元素 (可选类型)
print(names.contains("Bobby")) // 是否包含特定元素
names.sort() // 排序 (如果是可比较的类型)
names.reverse() // 反转顺序
5.2 字典 (Dictionaries)
字典是存储无序键值对的集合。每个值都与一个唯一的键相关联。键必须是相同类型的,值也必须是相同类型的。
-
声明与初始化:
swift
var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin Airport"] // 声明并初始化
var studentScores = [String: Int]() // 声明一个空的 String 到 Int 的字典
var emptyDict: [Int: Bool] = [:] // 声明一个指定类型的空字典
let colors = ["red": "#FF0000", "green": "#00FF00", "blue": "#0000FF"] // Swift 推断类型为 [String: String] -
访问与修改:
“`swift
print(airports[“YYZ”]) // 通过键访问值,返回一个可选类型 (Optional)
print(airports[“LHR”]) // 键不存在时返回 nilairports[“LHR”] = “London Heathrow” // 添加新的键值对
print(airports)airports[“YYZ”] = “Toronto International” // 修改现有键的值
print(airports)// 使用 updateValue(_:forKey:) 方法,会返回键的旧值(可选类型)
if let oldValue = airports.updateValue(“Dublin International”, forKey: “DUB”) {
print(“The old value for DUB was (oldValue).”)
}// 移除键值对
airports[“LHR”] = nil // 将键的值设为 nil 即可移除
print(airports)if let removedValue = airports.removeValue(forKey: “DUB”) { // 移除并返回旧值
print(“The removed airport name is (removedValue).”)
}
print(airports) // 现在 airports 字典为空
``
String?
注意:通过键访问字典元素返回的是一个可选类型(如或
Int?`),因为请求的键可能不存在。你需要安全地处理这个可选值(稍后会详细介绍)。 -
遍历:
“`swift
let capitals = [“France”: “Paris”, “Spain”: “Madrid”, “Germany”: “Berlin”]// 遍历键值对
for (country, capital) in capitals {
print(“The capital of (country) is (capital)”)
}// 只遍历键
for country in capitals.keys {
print(“Country: (country)”)
}// 只遍历值
for capital in capitals.values {
print(“Capital: (capital)”)
}// 可以将键或值转换为数组
let countryNames = Array(capitals.keys)
let capitalNames = Array(capitals.values)
print(countryNames)
print(capitalNames)
“`
5.3 集合 (Sets)
集合是存储相同类型值的无序集合。集合中的元素必须是唯一的,不能重复。集合通常用于判断元素是否存在以及执行集合运算。
-
声明与初始化: 集合类型写为
Set<Element>
,其中Element
是集合中允许存储的类型。
swift
var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"] // 声明并初始化
var oddDigits: Set = [1, 3, 5, 7, 9] // Swift 可以推断类型为 Set<Int>
var emptySet = Set<Character>() // 声明一个空的 Character 集合
注意:因为集合是无序的,所以用数组字面量创建集合时,其内部存储顺序与字面量中的顺序可能不同。 -
插入与移除:
“`swift
favoriteGenres.insert(“Jazz”) // 插入元素
print(favoriteGenres) // 顺序可能不同if let removedGenre = favoriteGenres.remove(“Classical”) { // 移除元素,返回被移除的元素(可选)
print(“(removedGenre)? I’m over it.”)
} else {
print(“I never much cared for that.”)
}
“` -
检查包含性:
swift
print(favoriteGenres.contains("Rock")) // true
print(favoriteGenres.contains("Funk")) // false -
遍历:
“`swift
for genre in favoriteGenres {
print(genre) // 遍历集合中的元素
}// 集合是无序的,但你可以对其进行排序以进行有序遍历
for genre in favoriteGenres.sorted() {
print(genre)
}
“` -
集合运算:
“`swift
let oddDigits: Set = [1, 3, 5, 7, 9]
let evenDigits: Set = [0, 2, 4, 6, 8]
let primeDigits: Set = [2, 3, 5, 7]// 交集: intersection(_:) – 两个集合中都存在的元素
print(oddDigits.intersection(primeDigits)) // {3, 5, 7}// 并集: union(_:) – 两个集合中的所有元素
print(oddDigits.union(evenDigits).sorted()) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]// 差集: subtracting(_:) – 在第一个集合中但不在第二个集合中的元素
print(oddDigits.subtracting(primeDigits).sorted()) // [1, 9]// 对称差集: symmetricDifference(_:) – 在任一集合中但不同时在两个集合中的元素
print(oddDigits.symmetricDifference(primeDigits).sorted()) // [1, 2, 9]
“`
集合还支持判断子集、超集和不相交等关系。
第六章:函数 – 组织与重用代码
函数是执行特定任务的代码块。通过将相关代码组织到函数中,你可以提高代码的可读性、可维护性和可重用性。
-
定义函数:
swift
func greet(person: String) -> String { // func 关键字定义函数,person 是参数名和外部参数名,String 是参数类型,-> String 表示返回 String 类型
let greeting = "Hello, " + person + "!"
return greeting // 使用 return 返回值
} -
调用函数:
“`swift
print(greet(person: “Anna”)) // 调用 greet 函数,参数需要写外部参数名
// 输出 “Hello, Anna!”let message = greet(person: “Brian”)
print(message) // 输出 “Hello, Brian!”
“` -
无参数函数:
swift
func sayHelloWorld() -> String {
return "Hello, world!"
}
print(sayHelloWorld()) // 输出 "Hello, world!" -
无返回值函数: 如果函数没有明确的返回值,它默认返回
Void
类型(一个空的元组()
)。可以省略返回类型或显式写-> Void
。
swift
func greetAgain(person: String) { // 没有 -> 返回类型
print("Hello again, \(person)!")
}
greetAgain(person: "Anna") // 输出 "Hello again, Anna!" -
多个参数:
swift
func addTwoNumbers(a: Int, b: Int) -> Int {
return a + b
}
print(addTwoNumbers(a: 5, b: 3)) // 输出 8 -
外部参数名与内部参数名: 默认情况下,函数的第一个参数只有内部参数名,后面的参数既有外部参数名(用于调用时)也有内部参数名(用于函数内部)。你可以在参数名前显式指定外部参数名,或使用
_
忽略外部参数名。
“`swift
// 第一个参数默认没有外部名,但这里显式加上了 from:
func greet(person: String, from hometown: String) -> String {
return “Hello (person)! Glad you could visit from (hometown).”
}
print(greet(person: “Bill”, from: “Cupertino”)) // 调用时必须写 from:// 忽略外部参数名
func multiply(_ num1: Int, by num2: Int) -> Int { // 使用 _ 忽略第一个参数的外部名
return num1 * num2
}
print(multiply(4, by: 2)) // 调用时第一个参数不写名字,第二个写 by:
“`
使用外部参数名可以使函数调用更具可读性,就像一个句子。 -
可变参数: 函数可以接受可变数量的同类型参数。参数名后跟
...
。
swift
func arithmeticMean(_ numbers: Double...) -> Double {
var total: Double = 0
for number in numbers {
total += number
}
return total / Double(numbers.count)
}
print(arithmeticMean(1, 2, 3, 4, 5)) // 输出 3.0
print(arithmeticMean(3, 8.25, 18.75)) // 输出 10.0 -
in-out 参数: 函数参数默认是常量。如果你想函数能够修改参数变量的值,并让这些修改在函数调用结束后仍然有效,可以使用
inout
关键字。传递inout
参数时,必须在变量名前加&
符号。
swift
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt) // 使用 & 传递变量引用
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// 输出 "someInt is now 107, and anotherInt is now 3"
第七章:结构体与类 – 构建复杂数据类型
结构体 (Structs) 和类 (Classes) 是 Swift 中定义自定义复杂数据类型的基础。它们都可以定义属性 (properties) 来存储值,定义方法 (methods) 来提供功能。
-
定义结构体: 使用
struct
关键字。
“`swift
struct Point {
var x: Double // 存储属性
var y: Double// 初始化器 (Initializer) - 用于创建结构体实例时设置属性的初始值 // 结构体有默认的逐一成员初始化器 // 也可以自定义 init(x: Double, y: Double) { self.x = x // self 代表当前结构体实例 self.y = y } // 方法 - 定义在结构体/类/枚举中的函数 func description() -> String { return "This point is at (\(x), \(y))" } // 变异方法 (Mutating Methods) - 如果方法需要修改结构体/枚举的属性,必须标记为 mutating mutating func moveBy(deltaX: Double, deltaY: Double) { x += deltaX y += deltaY }
}
“` -
创建结构体实例:
“`swift
var origin = Point(x: 0.0, y: 0.0) // 使用初始化器创建实例
print(origin.description()) // 调用方法origin.moveBy(deltaX: 1.0, deltaY: 2.0) // 调用变异方法修改实例
print(origin.description()) // 输出 “This point is at (1.0, 2.0)”
“` -
定义类: 使用
class
关键字。
“`swift
class Person {
var name: String
var age: Int// Designated Initializer (指定初始化器) - 类必须有一个或多个指定初始化器来确保所有存储属性都被赋值 init(name: String, age: Int) { self.name = name self.age = age } // Computed Property (计算属性) - 不存储值,而是通过计算其他属性得来 var description: String { return "\(name) is \(age) years old." } func celebrateBirthday() { age += 1 print("\(name) is now \(age) years old!") }
}
“` -
创建类实例:
swift
let john = Person(name: "John Doe", age: 30)
print(john.description) // 输出 "John Doe is 30 years old."
john.celebrateBirthday() // 调用方法
print(john.description) // 输出 "John Doe is now 31 years old."
7.1 结构体 vs 类:值类型 vs 引用类型
这是 Swift 中一个非常重要的概念:
- 结构体是值类型 (Value Types): 当你赋值或传递一个结构体实例时,实际上是拷贝了它的值。每个变量或常量都拥有自己独立的副本。修改一个副本不会影响另一个副本。
- 类是引用类型 (Reference Types): 当你赋值或传递一个类实例时,实际上是拷贝了对同一个实例的引用。多个变量或常量可能引用同一个实例。修改其中一个变量引用的实例,会影响所有引用同一个实例的变量。
理解这个区别对于避免意外的代码行为至关重要。
“`swift
// 值类型示例 (结构体)
struct Size {
var width: Int
var height: Int
}
var size1 = Size(width: 10, height: 20)
var size2 = size1 // size2 获得 size1 的一个副本
size1.width = 50 // 修改 size1
print(size1) // 输出 Size(width: 50, height: 20)
print(size2) // 输出 Size(width: 10, height: 20) – size2 未受影响
“`
“`swift
// 引用类型示例 (类)
class Rectangle {
var width: Int
var height: Int
init(width: Int, height: Int) {
self.width = width
self.height = height
}
}
var rect1 = Rectangle(width: 10, height: 20)
var rect2 = rect1 // rect2 和 rect1 现在引用同一个 Rectangle 实例
rect1.width = 50 // 修改 rect1 引用的实例
print(rect1.width) // 输出 50
print(rect2.width) // 输出 50 – rect2 引用的是同一个实例,所以也看到了修改
“`
选择使用哪个?
Swift 倾向于使用值类型(结构体、枚举)。通常情况下,当你在考虑定义自定义数据类型时,优先考虑结构体,除非需要类的以下特性:
- 继承 (Inheritance): 一个类可以继承另一个类的特性。
- 引用计数 (Reference Counting): 允许多个引用指向同一个类实例。
- 类型转换 (Type Casting): 可以在运行时检查和解释类实例的类型。
- 析构函数 (Deinitializers): 释放类实例占用的资源。
对于大多数简单的数据封装或不需要身份识别(多个变量引用同一对象并期望它们是同一个)的情况,结构体是更安全、更高效的选择。Swift 的许多内置类型(如 String, Array, Dictionary, Int, Double, Bool 等)都是值类型。
7.2 类继承
类可以从另一个类继承特性。被继承的类称为父类或基类,继承的类称为子类。
“`swift
class Vehicle { // 基类
var currentSpeed = 0.0
func makeNoise() {
// do nothing - an arbitrary vehicle doesn't necessarily make a noise
}
func accelerate(speed: Double) {
currentSpeed += speed
}
}
class Bicycle: Vehicle { // 子类继承 Vehicle
var hasBasket = false // 子类可以添加自己的属性
func ringBell() { // 子类可以添加自己的方法
print("Ding dong!")
}
}
let someVehicle = Vehicle()
someVehicle.accelerate(speed: 10.0)
print(someVehicle.currentSpeed) // 输出 10.0
let someBicycle = Bicycle()
someBicycle.accelerate(speed: 5.0) // Bicycle 继承了 Vehicle 的 accelerate 方法
print(someBicycle.currentSpeed) // 输出 5.0
someBicycle.ringBell() // Bicycle 使用自己的 ringBell 方法
“`
子类还可以重写 (Override) 父类的方法、属性、下标脚本等,使用 override
关键字。
“`swift
class Car: Vehicle {
var gear = 1
// 重写 makeNoise 方法
override func makeNoise() {
print("Vroom vroom!")
}
// 重写一个计算属性 (即使 Vehicle 没有实现,也可以重写)
// override var description: String {
// return super.description + " in gear \(gear)" // super 访问父类的实现
// }
}
let someCar = Car()
someCar.accelerate(speed: 20.0)
someCar.makeNoise() // 输出 “Vroom vroom!” (调用的是 Car 重写的版本)
“`
第八章:枚举 (Enums) – 定义相关值
枚举 (Enumerations) 定义一组相关的、有限的可能值。它们提供了一种类型安全的方式来处理一系列离散的选项。
-
定义枚举: 使用
enum
关键字。
swift
enum CompassPoint {
case north
case south
case east
case west
}
多个 case 可以写在同一行,用逗号隔开:enum Planet { case mercury, venus, earth, mars }
-
使用枚举:
swift
var directionToHead = CompassPoint.west // 指定类型
directionToHead = .east // 一旦变量类型确定,可以使用缩写 .caseName -
使用 switch 匹配枚举值:
swift
directionToHead = .south
switch directionToHead {
case .north:
print("Lots of planets have a north")
case .south:
print("Watch out for penguins")
case .east:
print("Where the sun rises")
case .west:
print("Where the skies are blue")
}
// 输出 "Watch out for penguins"
当switch
语句用于穷尽枚举的所有 case 时,可以省略default
分支。 -
原始值 (Raw Values): 枚举成员可以预设一个默认值,称为原始值。原始值可以是字符串、字符或任何整数或浮点类型。所有原始值必须是相同类型的。
“`swift
enum ASCIIControlCharacter: Character {
case tab = “\t”
case lineFeed = “\n”
case carriageReturn = “\r”
}enum Planet: Int { // Int 是原始值类型
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
// 如果只为第一个 case 指定原始值,后续 case 会自动递增 (Int) 或使用 case 名作为原始值 (String)
// 例如,mercury 是 1, venus 是 2, earth 是 3…
}// 访问原始值
let earthOrder = Planet.earth.rawValue // earthOrder 是 3
print(earthOrder)// 使用原始值初始化枚举实例 (返回一个可选类型,因为原始值可能找不到对应的 case)
let possiblePlanet = Planet(rawValue: 7) // possiblePlanet 是 Planet.uranus
if let seventhPlanet = possiblePlanet {
print(“The seventh planet is (seventhPlanet)”)
} else {
print(“There is no seventh planet.”)
}
“` -
关联值 (Associated Values): 枚举成员可以存储不同类型的关联值。这允许你将额外的信息与枚举成员关联起来,而这些信息在枚举定义时是未知的。
“`swift
enum Barcode {
case upc(Int, Int, Int, Int) // upc 条形码有关联的四个 Int 值
case qrCode(String) // qrCode 条形码有关联的一个 String 值
}// 创建带有关联值的枚举实例
var productBarcode = Barcode.upc(8, 85909, 51226, 3)
productBarcode = .qrCode(“ABCDEFGHIJKLMNOP”) // 修改为另一个 case 及关联值// 使用 switch 解包关联值
switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let check): // 解包关联值到常量
print(“UPC: (numberSystem), (manufacturer), (product), (check).”)
case .qrCode(let productCode): // 解包关联值到常量
print(“QR code: (productCode).”)
}
// 输出 “QR code: ABCDEFGHIJKLMNOP.”// 可以使用 var 代替 let 来解包关联值到变量
switch productBarcode {
case .upc(var numberSystem, let manufacturer, var product, let check):
numberSystem = 0 // 可以修改 numberSystem 和 product
// …
print(…)
case .qrCode(let productCode):
print(…)
}// 更简洁的关联值解包方式:如果 case 的所有关联值都解包为常量或都解包为变量,
// 可以在 case 名称前加上 let 或 var
switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
print(“UPC: (numberSystem), (manufacturer), (product), (check).”)
case let .qrCode(productCode):
print(“QR code: (productCode).”)
}
“`
第九章:Optionals (可选类型) – 处理值的缺失
Optionals 是 Swift 中最重要也是最独特的特性之一。它们用于处理值可能缺失的情况。一个可选类型变量要么包含一个值,要么包含 nil
(表示没有值)。
Swift 的 Optionals 设计解决了许多其他语言中常见的空指针引用问题,提高了代码的安全性。
-
定义可选类型: 在类型名称后面加上一个问号
?
。
“`swift
var serverResponseCode: Int? = 404 // 可以赋值一个 Int 值 404
serverResponseCode = nil // 也可以赋值 nil,表示没有值var surveyAnswer: String? // 默认值是 nil,无需显式赋值 nil
// surveyAnswer 现在是 nil
“` -
访问可选类型的值: 你不能直接使用可选类型的值,因为它可能包含
nil
。你需要先“解包”可选类型,以确定它是否包含值,并在包含值时访问它。 -
强制解包 (Forced Unwrapping): 在可选类型变量名后面加上一个感叹号
!
。如果可选类型是 nil,强制解包会导致运行时错误 (crash)。 应尽量避免使用强制解包,除非你绝对确定可选类型一定有值。
“`swift
let possibleNumber = “123”
let convertedNumber = Int(possibleNumber) // convertedNumber 的类型是 Int? (可选的 Int)
// 如果 possibleNumber 不是一个有效的整数字符串,convertedNumber 将是 nil// 如果你确定 convertedNumber 不是 nil…
// print(convertedNumber!) // 使用 ! 强制解包
“`
强烈建议避免不安全的强制解包! -
可选绑定 (Optional Binding): 使用
if let
或if var
安全地检查可选类型是否包含值,如果包含,则将值赋给一个临时常量或变量。
“`swift
let anotherPossibleNumber = “123”
if let actualNumber = Int(anotherPossibleNumber) {
// 如果 Int(anotherPossibleNumber) 不是 nil,就解包并赋给常量 actualNumber
print(“The string \”(anotherPossibleNumber)\” has an integer value of (actualNumber)”)
} else {
// 如果 Int(anotherPossibleNumber) 是 nil
print(“The string \”(anotherPossibleNumber)\” could not be converted to an integer”)
}
// 输出 “The string “123” has an integer value of 123″let yetAnotherString = “abc”
if let thirdNumber = Int(yetAnotherString) {
print(“…”) // 不会执行这里的代码
} else {
print(“The string \”(yetAnotherString)\” could not be converted to an integer”)
}
// 输出 “The string “abc” could not be converted to an integer”
你可以在同一个 `if let` 语句中绑定多个可选类型,用逗号隔开:
swift
if let first = Int(“1”), let second = Int(“2”), let third = Int(“3”) {
print(“All three numbers are valid: (first), (second), (third)”)
} // 只有所有可选绑定都成功时,才会执行 if 的代码块
“` -
可选链式调用 (Optional Chaining): 使用问号
?.
来访问可选类型的值的属性、方法或下标。如果可选类型为nil
,则整个表达式链会短路并返回nil
,而不会引发错误。如果可选类型有值,则进行解包并继续调用链。
“`swift
class Residence {
var numberOfRooms = 1
}class PersonOptional {
var residence: Residence? // residence 是一个可选类型
}let johnOptional = PersonOptional()
// johnOptional.residence 现在是 nil// 尝试访问 residence 的 numberOfRooms
// let roomCount = johnOptional.residence.numberOfRooms // 这会导致编译错误,因为 residence 是 Optional// 使用可选链式调用
if let roomCount = johnOptional.residence?.numberOfRooms { // 如果 residence 不是 nil,则访问 numberOfRooms
print(“John’s residence has (roomCount) room(s).”)
} else {
print(“Unable to retrieve the number of rooms.”) // residence 是 nil,所以执行这里
}
// 输出 “Unable to retrieve the number of rooms.”johnOptional.residence = Residence() // 现在给 residence 赋值一个 Residence 实例
if let roomCount = johnOptional.residence?.numberOfRooms { // 现在 residence 不为 nil
print(“John’s residence has (roomCount) room(s).”) // 访问 numberOfRooms 并解包成功
} else {
print(“Unable to retrieve the number of rooms.”)
}
// 输出 “John’s residence has 1 room(s).”
“`
可选链式调用的结果本身也是一个可选类型。 -
空合运算符 (Nil-Coalescing Operator):
(a ?? b)
。如果可选类型a
包含一个值,则解包a
并使用该值;否则,使用默认值b
。表达式a
必须是可选类型,表达式b
必须与a
解包后的类型相同。
“`swift
let defaultColorName = “red”
var userDefinedColorName: String? // 默认是 nilvar colorNameToUse = userDefinedColorName ?? defaultColorName // userDefinedColorName 是 nil,使用 defaultColorName
print(colorNameToUse) // 输出 “red”userDefinedColorName = “green”
colorNameToUse = userDefinedColorName ?? defaultColorName // userDefinedColorName 有值,使用它的值
print(colorNameToUse) // 输出 “green”
“` -
隐式解包可选类型 (Implicitly Unwrapped Optionals): 在类型名称后面加上一个感叹号
!
。隐式解包可选类型在第一次赋值后,可以像非可选类型一样使用,无需每次都解包。但是,如果在使用时它为 nil,则会引发运行时错误。 它们主要用于某些特定场景,例如初始化器中必须有一个属性但在初始化完成前无法设置其值的情况(如 UI 控件)。
“`swift
let myString: String! = “An implicitly unwrapped optional string.”
let meaningOfLife: Int! = 42let greeting = myString // 可以像普通 String 一样使用
print(greeting) // 输出 “An implicitly unwrapped optional string.”// 如果在使用时是 nil,会导致 crash
// var nullableString: String! = nil
// print(nullableString) // 运行时错误!
``
if let
尽量避免使用隐式解包可选类型,除非有明确的理由。明确处理可选类型(使用或
guard let`)是更安全的做法。
第十章:下一步做什么?
恭喜你!你已经了解了 Swift 语言的核心基础知识,包括:
- 变量、常量和基本数据类型
- 运算符和类型安全
- 控制流 (if, switch, for-in, while)
- 集合类型 (数组, 字典, 集合)
- 函数 (定义、参数、返回值)
- 结构体与类 (值类型 vs 引用类型)
- 枚举
- 可选类型 (Optionals) 及其安全处理方法
这只是 Swift 世界的冰山一角。接下来,你可以继续学习:
- 更高级的 Swift 特性:
- Closures (闭包): 自包含的函数代码块,可以在代码中传递和使用。
- Protocols (协议): 定义一套方法、属性或其他要求,任何遵循该协议的类型(类、结构体、枚举)都需要实现这些要求。
- Extensions (扩展): 允许你向现有类、结构体、枚举或协议添加新功能,而无需访问其原始定义。
- Error Handling (错误处理): Swift 强大的错误处理机制(使用
try
,catch
,throw
)。 - Generics (泛型): 编写灵活、可重用的代码,可以处理任何类型,同时保持类型安全。
- Concurrency (并发): 使用
async/await
等现代特性编写异步和并行代码。
- 面向 Apple 平台开发:
- SwiftUI: 新一代声明式 UI 框架,用于在所有 Apple 平台上构建用户界面。
- UIKit (iOS/tvOS) / AppKit (macOS): 老牌的命令式 UI 框架。
- 各种框架: Core Data (数据持久化), Core Animation (动画), SpriteKit (2D 游戏), SceneKit (3D 图形), Core ML (机器学习) 等等。
- 实践与项目:
- 尝试在 Playground 中编写一些小脚本,练习你学过的语法。
- 思考一个小应用的想法(比如一个简单的待办事项列表、计算器或猜数字游戏),并尝试用 Swift 构建它。从简单的功能开始。
- 参与开源项目或尝试解决一些编程挑战网站上的 Swift 问题。
- 学习资源:
- 《The Swift Programming Language》官方文档: 这是最权威、最详细的 Swift 学习资料,虽然对初学者可能有点深,但它是宝贵的参考。
- Apple 官方教程: Apple 开发者网站提供了许多关于 Swift 和其框架的优秀教程和文档。
- 在线课程: Coursera, Udemy, Ray Wenderlich 等平台有很多 Swift 开发课程。
- 社区论坛: Stack Overflow, Apple Developer Forums 等地方可以提问和交流。
总结
Swift 是一门强大、安全且富有表现力的现代编程语言,它为构建跨 Apple 平台及其他领域的应用提供了坚实的基础。从变量和常量到结构体、类和可选类型,Swift 的设计旨在帮助你编写出更少错误、更高性能、更易于维护的代码。
学习一门新的编程语言需要时间和实践。不要害怕犯错,多动手写代码,多尝试不同的例子,多查阅官方文档和社区资源。随着你对 Swift 的深入理解和实践经验的积累,你将能够解锁更多的可能性,创造出令人惊叹的软件。
现在,打开你的 Xcode Playground,开始你的 Swift 编程之旅吧!祝你学习愉快!