Apple Swift 编程入门指南
Apple Swift 编程入门指南:开启你的 iOS 与 macOS 开发之旅
引言:拥抱现代、强大且直观的编程语言
在当今数字化的世界里,移动应用和桌面软件已成为我们生活和工作中不可或缺的一部分。而在这背后,强大的编程语言驱动着创新。Apple 公司于 2014 年全球开发者大会 (WWDC) 上推出的 Swift 语言,正是这样一颗耀眼的新星。它凭借其现代化的设计、卓越的性能、高度的安全性以及相对平易近人的语法,迅速成为开发 iOS, iPadOS, macOS, watchOS 和 tvOS 应用的首选语言,并逐渐在服务器端、机器学习等领域崭露头角。
对于希望进入 Apple 生态系统开发的初学者,或者寻求掌握一门新技能的开发者来说,学习 Swift 无疑是一个明智的选择。本指南旨在为你提供一个全面而详细的 Swift 编程入门路线图,从基础概念到核心特性,帮助你稳步踏上 Swift 开发之路。这趟旅程需要耐心和实践,但 Swift 的优雅和强大一定会让你觉得不虚此行。
第一章:准备启程 —— 环境搭建与初识 Swift
1.1 为何选择 Swift?
在深入学习之前,我们先简单了解 Swift 的核心优势:
- 现代 (Modern): Swift 吸收了许多现代编程语言的优秀特性,如类型推断、泛型、闭包、元组等,使得代码更简洁、表达力更强。
- 安全 (Safe): Swift 在设计上极其注重安全性。例如,它的可选类型 (Optionals) 机制强制开发者处理值可能缺失 (nil) 的情况,大大减少了运行时因空指针引起的崩溃。变量在使用前必须初始化,数组越界等常见错误也更容易被捕获。
- 快速 (Fast): Swift 使用高性能的 LLVM 编译器进行编译,其性能目标是与 C++ 和 Objective-C 相媲美,甚至在某些场景下超越它们。
- 交互性强 (Interactive): Xcode 中的 Playgrounds 功能允许你实时看到 Swift 代码的运行结果,这对于学习、实验和快速原型设计非常有帮助。
- 开源 (Open Source): Swift 是一门开源语言,拥有活跃的社区支持,你可以在 swift.org 上找到它的源代码、开发动态和社区资源。这意味着它不仅限于 Apple 平台,也可以在 Linux 等其他系统上运行。
- 与 Objective-C 共存: Swift 可以与 Apple 平台长期使用的 Objective-C 语言无缝协作,允许在同一个项目中使用两种语言,方便逐步迁移旧项目或利用现有的 Objective-C 库。
1.2 开发环境:Xcode 与 Playgrounds
学习 Swift 最主要的工具是 Apple 官方提供的集成开发环境 (IDE) —— Xcode。
- 安装 Xcode: 你可以从 Mac App Store 免费下载并安装最新版本的 Xcode。安装过程可能需要较长时间,因为它包含了完整的 macOS 和 iOS SDK (软件开发工具包) 以及各种开发工具。
- 初识 Xcode: 打开 Xcode 后,你会看到一个欢迎界面。对于初学者,最常接触的两个选项是:
- Get started with a Playground: 这是学习 Swift 语法的理想之地。Playground 提供了一个交互式的环境,你编写的每一行代码都会立即执行,并在侧边栏显示结果。这极大地降低了学习门槛,让你能快速实验和验证想法。
- Create a new Xcode project: 当你准备开始构建实际的应用程序时,就需要创建项目了。Xcode 提供了多种项目模板,如 App, Framework, Library 等。
- 使用 Playgrounds: 强烈建议初学者从 Playground 开始。创建一个新的 Playground (File > New > Playground...),选择一个模板 (通常 Blank 就足够了),然后就可以开始编写你的第一行 Swift 代码了。
第二章:Swift 基础语法 —— 构建代码的基石
2.1 基本概念
- 注释 (Comments): 用于解释代码,编译器会忽略它们。
- 单行注释:
// 这是一行注释
- 多行注释:
/* 这是\n多行注释 */
- 单行注释:
- 分号 (Semicolons): Swift 不强制要求在每行代码末尾加分号。只有当你想在同一行写多条语句时才需要用分号隔开。
- 标识符 (Identifiers): 用于命名常量、变量、函数、类型等的名称。可以包含 Unicode 字符,但不能以数字开头,不能包含空格、数学符号、箭头等,且不能与 Swift 关键字冲突。
2.2 常量 (Constants) 与变量 (Variables)
这是编程中最基本的概念,用于存储数据。
- 常量 (
let
): 一旦赋值后,其值就不能再改变。推荐优先使用常量,除非你明确知道值需要改变,这有助于提高代码的安全性和可预测性。
let maximumLoginAttempts = 10
let welcomeMessage = "Hello, Swift!"
- 变量 (
var
): 其值可以在声明后被修改。
var currentLoginAttempt = 0
var score = 100
score = 110 // 合法
2.3 类型推断 (Type Inference) 与显式类型 (Explicit Types)
Swift 是一门强类型语言,意味着每个常量或变量都有一个明确的类型。但 Swift 拥有强大的类型推断能力,编译器通常能根据你赋的初始值自动推断出类型。
- 类型推断:
let pi = 3.14159 // Swift 推断 pi 为 Double 类型
var message = "Loading..." // Swift 推断 message 为 String 类型
- 显式类型: 如果编译器无法推断,或者你希望明确指定类型,可以使用冒号 (
:
) 后跟类型名称的方式。
let explicitDouble: Double = 70.0
var playerHealth: Int = 100
2.4 常见数据类型 (Common Data Types)
- Int: 整数类型,用于表示没有小数部分的数字。根据平台 (32位或64位),它的大小会不同 (通常是
Int64
)。
let age: Int = 30
- Double & Float: 浮点数类型,用于表示带小数的数字。
Double
精度更高 (64位),Float
精度较低 (32位)。通常推荐使用Double
。
let averageScore: Double = 88.5
let temperature: Float = 23.4
- Bool: 布尔类型,只有两个可能的值:
true
(真) 和false
(假)。常用于条件判断。
let isLoggedIn: Bool = true
var hasFinished: Bool = false
- String: 字符串类型,用于表示文本数据。使用双引号 (
"
) 包裹。
let greeting: String = "Welcome to Swift!"
let multilineString = """
This is a string
that spans multiple
lines.
"""
字符串支持拼接 (+
) 和插值 (\()
):
let firstName = "Taylor"
let lastName = "Swift"
let fullName = firstName + " " + lastName // 拼接
let profile = "Name: \(fullName), Age: \(age)" // 插值,更推荐
- Character: 单个字符类型。
let initial: Character = "S"
-
元组 (Tuple): 将多个不同类型的值组合成一个复合值。元组的值可以是命名的,也可以通过索引访问。
let httpStatus = (404, "Not Found")
print("Status code: \(httpStatus.0)")
print("Status message: \(httpStatus.1)")
let namedTuple = (code: 200, message: "OK")
print("Code: \(namedTuple.code)")
2.5 运算符 (Operators)
Swift 支持大多数标准 C 语言的运算符,并进行了一些改进。
- 赋值运算符 (
=
): 将右侧的值赋给左侧的变量/常量。let b = 10; var a = 5; a = b
- 算术运算符 (
+
,-
,*
,/
,%
): 加、减、乘、除、取余。注意 Swift 不允许数值类型溢出 (除非使用特定的溢出运算符)。整数除法会舍弃小数部分。
let sum = 5 + 3 // 8
let difference = 10 - 4 // 6
let product = 2 * 6 // 12
let quotient = 10 / 3 // 3
let remainder = 10 % 3 // 1
- 复合赋值运算符 (
+=
,-=
,*=
,/=
,%=
):a += 2
等价于a = a + 2
。 - 比较运算符 (
==
,!=
,>
,<
,>=
,<=
): 返回一个布尔值 (true
或false
)。
1 == 1 // true
2 != 1 // true
2 > 1 // true
- 逻辑运算符 (
!
,&&
,||
): 逻辑非 (NOT)、逻辑与 (AND)、逻辑或 (OR)。
let isRaining = true
let isCold = false
if !isCold { print("It's not cold.") }
if isRaining && isCold { print("Wear a coat and take an umbrella.") }
if isRaining || isCold { print("Weather might be unpleasant.") }
- 范围运算符 (Range Operators):
- 闭区间 (
a...b
): 包含 a 和 b。for i in 1...5 { print(i) } // 输出 1 2 3 4 5
- 半开区间 (
a..<b
): 包含 a,但不包含 b。for i in 1..<5 { print(i) } // 输出 1 2 3 4
- 单侧区间 (
a...
,...b
,..<b
): 用于数组切片等。
- 闭区间 (
- 三元条件运算符 (
a ? b : c
): 如果a
为true
,则表达式的值为b
,否则为c
。
let contentHeight = 40
let rowHeight = contentHeight + (hasHeader ? 50 : 20)
- 空合运算符 (
a ?? b
): 用于处理可选类型 (后面会讲)。如果a
不是nil
,则解包a
并返回其值;如果a
是nil
,则返回默认值b
。b
的类型必须与a
解包后的类型匹配。
let defaultColorName = "red"
var userDefinedColorName: String? // 可选类型,初始为 nil
let colorNameToUse = userDefinedColorName ?? defaultColorName // "red"
第三章:集合类型 (Collection Types)
用于存储一组值的容器。Swift 提供了三种主要的集合类型:数组 (Array)、字典 (Dictionary) 和集合 (Set)。它们都是泛型的,可以存储特定类型的值。
3.1 数组 (Array)
有序的值的集合。同一个数组中的元素类型必须相同。
- 创建数组:
var emptyIntArray: [Int] = []
var shoppingList: [String] = ["Eggs", "Milk"]
var scores = [98, 85, 92]
// 类型推断为 [Int]
let fixedSizeArray = Array(repeating: 0.0, count: 3) // [0.0, 0.0, 0.0]
- 访问和修改:
print("First item: \(shoppingList[0])") // "Eggs"
shoppingList[0] = "Six eggs"
shoppingList.append("Flour")
shoppingList += ["Baking Powder"]
shoppingList.insert("Chocolate Spread", at: 0)
let removedItem = shoppingList.remove(at: 1)
print("Number of items: \(shoppingList.count)")
if shoppingList.isEmpty { ... }
- 遍历数组:
for item in shoppingList { print(item) }
for (index, value) in shoppingList.enumerated() { print("Item \(index + 1): \(value)") }
3.2 字典 (Dictionary)
无序的键值对 (key-value pair) 集合。每个键 (key) 必须是唯一的,并且类型必须是可哈希的 (如 String, Int, Double, Bool 等)。值 (value) 的类型必须相同。
- 创建字典:
var emptyStringDict: [String: String] = [:]
var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
var responseCodes = [200: "OK", 404: "Not Found"] // 推断为 [Int: String]
- 访问和修改:
print("Airport name for YYZ: \(airports["YYZ"]!)") // 注意:字典查找返回可选类型,这里强制解包 (不推荐,后面讲)
if let airportName = airports["DUB"] { print("Airport name: \(airportName)") } // 安全的方式
airports["LHR"] = "London Heathrow"
airports["YYZ"] = "Toronto Pearson International"
// 更新值
airports["DUB"] = nil // 删除键值对
if let removedValue = airports.removeValue(forKey: "LHR") { ... }
print("Number of airports: \(airports.count)")
- 遍历字典:
for (airportCode, airportName) in airports { print("\(airportCode): \(airportName)") }
for airportCode in airports.keys { print("Code: \(airportCode)") }
for airportName in airports.values { print("Name: \(airportName)") }
3.3 集合 (Set)
无序的、唯一值的集合。同一个集合中的元素类型必须相同且可哈希。主要用于快速判断一个元素是否存在,以及执行集合运算 (交集、并集、差集等)。
- 创建集合:
var emptyLetterSet: Set<Character> = []
var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
// 注意:不能直接用字面量推断 Set,会默认为 Array
// var numbers = {1, 2, 3} // 错误
var oddNumbers: Set = [1, 3, 5, 7, 9]
// 显式声明类型 - 访问和修改:
favoriteGenres.insert("Jazz")
if favoriteGenres.contains("Funk") { ... }
if let removedGenre = favoriteGenres.remove("Rock") { ... }
print("Number of genres: \(favoriteGenres.count)")
- 集合运算:
let evenNumbers: Set = [0, 2, 4, 6, 8]
let primeNumbers: Set = [2, 3, 5, 7]
let unionSet = oddNumbers.union(evenNumbers).sorted() // 并集 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
let intersectionSet = oddNumbers.intersection(primeNumbers).sorted() // 交集 [3, 5, 7]
let differenceSet = oddNumbers.subtracting(primeNumbers).sorted() // 差集 [1, 9]
let symmetricDiffSet = oddNumbers.symmetricDifference(primeNumbers).sorted() // 对称差集 [1, 2, 9]
- 遍历集合:
for genre in favoriteGenres.sorted() { print(genre) }
// 排序后遍历
第四章:控制流 (Control Flow)
控制代码执行顺序的结构。
4.1 条件语句 (Conditional Statements)
if
,else if
,else
: 根据条件执行不同的代码块。
let temperatureInCelsius = 30
if temperatureInCelsius <= 0 {
print("It's freezing!")
} else if temperatureInCelsius < 15 {
print("It's cold.")
} else if temperatureInCelsius < 25 {
print("It's warm.")
} else {
print("It's hot!")
}
-
switch
: 对一个值进行多种可能情况的匹配。Swift 的switch
非常强大:- 不需要
break
:默认情况下,匹配到一个case
后就会自动退出switch
语句 (不会贯穿 Fallthrough)。如果需要贯穿,需显式使用fallthrough
关键字。 - 必须是详尽的 (Exhaustive): 必须覆盖所有可能的值,或者提供一个
default
分支。 - 可以匹配多种类型:不仅限于整数和字符串,可以匹配任何类型的值。
- 可以匹配区间、元组、进行值绑定 (Value Binding) 和使用
where
子句添加额外条件。
let someCharacter: Character = "z"
switch someCharacter {
case "a", "e", "i", "o", "u":
print("\(someCharacter) is a vowel")
case "b", "c", ..., "z": // 可以使用范围
print("\(someCharacter) is a consonant")
default:
print("\(someCharacter) is not a letter")
}
let approximateCount = 62
switch approximateCount {
case 0:
naturalCount = "no"
case 1..<5:
naturalCount = "a few"
case 5..<12:
naturalCount = "several"
case 12..<100:
naturalCount = "dozens of"
case 100..<1000:
naturalCount = "hundreds of"
default:
naturalCount = "many"
}
let somePoint = (1, 1)
switch somePoint {
case (0, 0):
print("Origin")
case (_, 0): // 匹配 x 轴上的点
print("On the x-axis with x = \(somePoint.0)")
case (0, _): // 匹配 y 轴上的点
print("On the y-axis with y = \(somePoint.1)")
case (-2...2, -2...2): // 匹配区域内的点
print("Inside the box")
default:
print("Outside of the box")
}
// 值绑定与 where 子句
let anotherPoint = (2, 0)
switch anotherPoint {
case (let x, 0): // 绑定 x
print("On the x-axis with an x value of \(x)")
case (0, let y): // 绑定 y
print("On the y-axis with a y value of \(y)")
case let (x, y) where x == y: // 绑定 x, y 并添加条件
print("On the line x == y at (\(x), \(y))")
case let (x, y): // 绑定 x, y (捕获所有其他情况)
print("Somewhere else at (\(x), \(y))")
}
- 不需要
4.2 循环语句 (Looping Statements)
-
for-in
: 遍历序列 (如范围、数组、字典、集合、字符串等)。
let names = ["Anna", "Alex", "Brian", "Jack"]
for name in names { print("Hello, \(name)!") }
let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
for (animalName, legCount) in numberOfLegs { print("\(animalName)s have \(legCount) legs") }
for index in 1...5 { print("\(index) times 5 is \(index * 5)") }
*while
: 当条件为true
时,重复执行代码块。先判断条件,再执行。
var counter = 5
while counter > 0 {
print("Countdown: \(counter)")
counter -= 1
}
*repeat-while
: 先执行一次代码块,然后当条件为true
时,重复执行。至少执行一次。
var diceRoll = 0
repeat {
diceRoll = Int.random(in: 1...6)
print("You rolled a \(diceRoll)")
} while diceRoll != 6
print("You rolled a 6!")
4.3 控制转移语句 (Control Transfer Statements)
continue
: 跳过当前循环的剩余部分,直接开始下一次迭代。break
: 立即终止整个循环语句或switch
语句的执行。fallthrough
: 在switch
语句中,强制执行下一个case
的代码块 (不常用)。return
: 从函数或方法中返回值并退出。throw
: 抛出一个错误 (用于错误处理)。- 标签语句 (Labeled Statements): 可以给循环或
switch
语句添加标签,break
或continue
可以指定跳转到哪个标签标记的语句。
第五章:函数 (Functions)
组织可重用代码块的基本方式。
5.1 定义与调用函数
func functionName(parameterName: ParameterType) -> ReturnType {
// 函数体
// ...
return value // 如果有返回值
}
- 无参数,无返回值:
func sayHello() {
print("Hello!")
}
sayHello()
- 有参数,无返回值:
func greet(person: String) {
print("Hello, \(person)!")
}
greet(person: "Dave")
- 有参数,有返回值:
func addTwoInts(_ a: Int, _ b: Int) -> Int { // 使用 _ 省略外部参数名
return a + b
}
let sum = addTwoInts(5, 3)
- 无参数,有返回值:
func getRandomNumber() -> Int {
return Int.random(in: 1...100)
}
let randomNumber = getRandomNumber()
- 多返回值 (使用元组):
func findMinMax(array: [Int]) -> (min: Int, max: Int)? { // 返回可选元组
if array.isEmpty { return nil }
var currentMin = array[0]
var currentMax = array[0]
for value in array[1..<array.count] {
if value < currentMin { currentMin = value }
else if value > currentMax { currentMax = value }
}
return (currentMin, currentMax)
}
if let bounds = findMinMax(array: [8, -6, 2, 109, 3, 71]) {
print("Min is \(bounds.min) and max is \(bounds.max)")
}
5.2 参数标签 (Argument Labels) 和参数名 (Parameter Names)
- 参数标签: 调用函数时使用的名称。
- 参数名: 函数内部使用的名称。
- 默认情况下,参数标签和参数名相同。
- 可以指定不同的参数标签和参数名:
func someFunction(argumentLabel parameterName: Type)
- 可以使用下划线 (
_
) 省略参数标签:func someFunction(_ parameterName: Type)
-
第一个参数默认省略参数标签 (除非显式提供)。
func greet(person name: String, from hometown: String) {
print("Hello \(name)! Glad you could visit from \(hometown).")
}
greet(person: "Bill", from: "Cupertino") // 调用时使用参数标签 person 和 from
5.3 默认参数值 (Default Parameter Values)
可以为参数提供默认值,调用时可以省略该参数。
func power(base: Int, exponent: Int = 2) -> Int { ... }
power(base: 3) // 9 (exponent 使用默认值 2)
power(base: 2, exponent: 3) // 8
5.4 可变参数 (Variadic Parameters)
允许函数接受零个或多个指定类型的值。参数名后加 ...
。函数内部会将这些值作为一个数组来访问。一个函数最多只能有一个可变参数。
func average(_ numbers: Double...) -> Double {
var total: Double = 0
for number in numbers { total += number }
return numbers.isEmpty ? 0 : total / Double(numbers.count)
}
average(1, 2, 3, 4, 5) // 3.0
average(3, 8.25, 18.75) // 10.0
5.5 输入输出参数 (In-Out Parameters)
函数默认不能修改传入的参数值 (值传递)。如果希望函数能修改参数的值,并且这些修改在函数调用结束后仍然生效,可以将参数声明为 inout
。调用时,需要传递变量,并在变量名前加上 &
。
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)") // 输出 107 和 3
5.6 函数类型 (Function Types)
每个函数都有特定的函数类型,由参数类型和返回类型组成。可以将函数赋值给变量或常量,也可以作为参数传递给其他函数,或作为函数的返回值。
func add(_ a: Int, _ b: Int) -> Int { return a + b }
func multiply(_ a: Int, _ b: Int) -> Int { return a * b }
var mathFunction: (Int, Int) -> Int = add // mathFunction 的类型是 (Int, Int) -> Int
print("Result: \(mathFunction(2, 3))") // 输出 5
mathFunction = multiply
print("Result: \(mathFunction(2, 3))") // 输出 6
func printMathResult(_ operation: (Int, Int) -> Int, _ a: Int, _ b: Int) {
print("Result: \(operation(a, b))")
}
printMathResult(add, 5, 3) // 输出 8
第六章:可选类型 (Optionals) —— 处理值缺失的艺术
这是 Swift 最核心和最具特色的功能之一,用于处理值可能不存在 (为 nil
) 的情况。nil
在 Swift 中表示“没有值”,它不是指针,而是一个特殊的值。
6.1 可选类型的声明
在类型名称后加上问号 (?
) 来声明一个可选类型。
var optionalString: String? = "Hello"
var optionalInt: Int?
// 默认值为 nil
6.2 为何需要可选类型?
很多操作可能会失败并返回“没有值”,例如:
* 字典查找:airports["XYZ"]
可能找不到对应的机场名。
* 类型转换:Int("hello")
无法将字符串 "hello" 转换为整数。
* 访问可能为空的属性或调用可能失败的方法。
可选类型强制你思考并处理这些 nil
的情况,防止在运行时因访问 nil
而导致程序崩溃 (这是 Objective-C 和许多其他语言中常见的错误来源)。
6.3 解包可选类型 (Unwrapping Optionals)
要使用可选类型中包含的值,你需要先“解包”(unwrap) 它。有几种安全的方式:
-
可选绑定 (
if let
/guard let
): 最安全、最常用的方式。尝试解包可选值,如果成功 (值不是nil
),则将解包后的值赋给一个临时常量或变量,并在if
或guard
的代码块中使用它。
var serverResponseCode: Int? = 404
if let code = serverResponseCode {
print("Received response code: \(code)") // 只有当 serverResponseCode 不是 nil 时执行
} else {
print("No response code received.")
}
func process(code: Int) { print("Processing code \(code)...") }
guard let validCode = serverResponseCode else {
print("Cannot process: Invalid response code.")
// guard 语句的 else 分支必须退出当前作用域 (如 return, break, throw)
return
}
// 在 guard 语句之后,validCode 就可以在当前作用域的剩余部分使用了
process(code: validCode)
-
空合运算符 (
??
): 提供一个默认值,当可选值为nil
时使用。
let userColorPreference: String? = nil
let colorToDisplay = userColorPreference ?? "DefaultBlue"
print(colorToDisplay) // "DefaultBlue"
-
可选链 (
?.
): 如果你想调用可选值的属性、方法或下标,但不想在每次调用前都写if let
,可以使用可选链。如果可选值为nil
,整个调用链会优雅地失败并返回nil
,而不会崩溃。如果可选值非nil
,则调用成功,返回结果仍然是可选类型 (因为链中任何一步都可能返回nil
)。
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}
let john = Person()
// let roomCount = john.residence!.numberOfRooms // 危险!如果 residence 是 nil 会崩溃
if let roomCount = john.residence?.numberOfRooms { // 使用可选链
print("John's residence has \(roomCount) room(s).")
} else {
print("Unable to retrieve the number of rooms.") // 因为 john.residence 是 nil
}
john.residence = Residence()
if let roomCount = john.residence?.numberOfRooms {
print("John's residence now has \(roomCount) room(s).") // 输出 1
}
-
强制解包 (
!
): 在可选类型变量/常量名后加感叹号 (!
)。这会直接访问可选值,但 如果此时可选值为nil
,程序会立即崩溃! 只有在你 绝对确定 可选值不可能为nil
的情况下才应使用它。通常应避免使用。
let assumedString: String! = "An implicitly unwrapped optional string." // 隐式解包可选类型
let implicitString: String = assumedString // 不需要解包,但如果 assumedString 是 nil 也会崩溃
let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // 强制解包,如果 possibleString 为 nil 则崩溃
第七章:结构体 (Structures) 与类 (Classes)
Swift 中创建自定义数据类型的两种主要方式。它们有很多相似之处 (定义属性、方法、下标、构造器、扩展、遵循协议),但也有关键区别。
7.1 共同点
- 定义属性 (Properties) 来存储值。
- 定义方法 (Methods) 来提供功能。
- 定义下标 (Subscripts) 来通过下标语法访问值。
- 定义构造器 (Initializers) 来设置初始状态。
- 可以通过扩展 (Extensions) 来增加功能。
- 可以遵循协议 (Protocols) 来提供标准功能。
7.2 定义语法
struct Resolution {
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
}
7.3 实例创建
使用构造器语法创建实例。
let someResolution = Resolution()
// 使用默认构造器
let someVideoMode = VideoMode()
// 使用默认构造器
let vga = Resolution(width: 640, height: 480) // 使用成员构造器 (结构体自动获得)
7.4 属性访问
使用点语法 (.
) 访问实例的属性。
print("The width of someResolution is \(someResolution.width)") // 0
someVideoMode.resolution.width = 1280
7.5 关键区别:值类型 (Value Types) vs 引用类型 (Reference Types)
- 结构体 (Struct) 和枚举 (Enum) 是值类型: 当它们被赋值给新的常量或变量,或者作为参数传递给函数时,它们的值会被 复制。每个实例都拥有自己独立的数据副本。
var hd = Resolution(width: 1920, height: 1080)
var cinema = hd // cinema 得到的是 hd 的一个副本
cinema.width = 2048
print("hd width is still \(hd.width)") // 输出 1920 (hd 未受影响)
-
类 (Class) 是引用类型: 当它们被赋值给新的常量或变量,或者作为参数传递给函数时,传递的是对 同一个 已存在实例的 引用 (或称为“指针”)。多个常量或变量可以指向同一个类的实例。修改其中一个引用的实例会影响到所有指向该实例的引用。
let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0
let alsoTenEighty = tenEighty // alsoTenEighty 指向与 tenEighty 相同的 VideoMode 实例
alsoTenEighty.frameRate = 30.0
print("The frameRate property of tenEighty is now \(tenEighty.frameRate)") // 输出 30.0
-
恒等运算符 (
===
,!==
): 用于判断两个类类型的常量或变量是否引用同一个实例。
if tenEighty === alsoTenEighty { print("Referencing the same VideoMode instance.") }
7.6 何时选择结构体,何时选择类?
Apple 的一般建议是:优先使用结构体。
- 使用结构体的情况:
- 主要目的是封装少量相关简单数据值。
- 期望实例在赋值或传递时被复制,而不是共享引用。
- 不需要继承其他类的属性或行为。
- 实例的数据不需要在多个地方同步改变。
(例如:坐标点、尺寸、颜色、配置信息等)
- 使用类的情况:
- 需要使用 Objective-C 的互操作性。
- 需要控制实例的身份 (使用
===
判断是否为同一对象)。 - 需要创建继承层次结构。
- 实例需要在多个地方共享并同步状态。
(例如:视图控制器、网络请求管理器、数据模型对象如果需要在多处共享状态)
第八章:属性 (Properties)
关联到特定类、结构体或枚举的值。
- 存储属性 (Stored Properties): 存储常量或变量作为实例的一部分。只能用于类和结构体。
struct FixedLengthRange {
var firstValue: Int // 存储属性
let length: Int // 常量存储属性
}
- 计算属性 (Computed Properties): 不直接存储值,而是提供 getter 和可选的 setter 来间接获取和设置其他属性或值。可以用于类、结构体和枚举。
struct Point { var x = 0.0, y = 0.0 }
struct Size { var width = 0.0, height = 0.0 }
struct Rect {
var origin = Point()
var size = Size()
var center: Point { // 计算属性
get { // getter
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set(newCenter) { // setter (可选)
origin.x = newCenter.x - (size.width / 2)
origin.y = newCenter.y - (size.height / 2)
}
// 如果只有 getter,可以省略 get {},称为只读计算属性
// var area: Double { return size.width * size.height }
}
}
- 属性观察器 (Property Observers): 监控和响应属性值的变化。可以添加到自定义的存储属性上 (非 lazy),以及从父类继承来的存储属性和计算属性上。
willSet
: 在新值被存储之前调用。新值作为常量参数传入 (默认名newValue
)。didSet
: 在新值被存储后立即调用。旧值作为参数传入 (默认名oldValue
)。
class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}
}
- 类型属性 (Type Properties): 属于类型本身,而不是类型的任何一个实例。使用
static
关键字定义 (对于类,也可以用class
关键字来允许子类重写计算类型属性)。
struct AudioChannel {
static let thresholdLevel = 10 // 静态存储属性
static var maxInputLevelForAllChannels = 0 // 静态变量存储属性
var currentLevel: Int = 0 { ... }
}
AudioChannel.maxInputLevelForAllChannels = 5
// 通过类型名访问
第九章:方法 (Methods)
与特定类型关联的函数。
- 实例方法 (Instance Methods): 属于类、结构体或枚举的实例。需要先创建实例才能调用。可以访问和修改实例属性,调用其他实例方法。
self
关键字用于引用当前实例 (通常可以省略)。
class Counter {
var count = 0
func increment() { self.count += 1 }
func increment(by amount: Int) { count += amount }
func reset() { count = 0 }
}
let counter = Counter()
counter.increment()
counter.increment(by: 5)
- 值类型中的可变方法 (Mutating Methods): 默认情况下,值类型 (结构体、枚举) 的实例方法不能修改实例的属性。如果需要修改,必须在
func
关键字前加上mutating
。
struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
// 甚至可以给 self 赋一个全新的实例// self = Point(x: x + deltaX, y: y + deltaY)
}}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveBy(x: 2.0, y: 3.0)`
- 类型方法 (Type Methods): 属于类型本身,而不是实例。使用
static
关键字定义 (对于类,也可以用class
关键字来允许子类重写)。通过类型名调用。
class SomeClass {
class func someTypeMethod() {
// 类型方法实现
print("Called type method")
}
}
SomeClass.someTypeMethod()
第十章:初步探索更深层次
掌握了以上基础后,你已经具备了编写简单 Swift 程序的能力。接下来可以开始探索一些更高级但同样重要的概念:
- 构造过程 (Initialization): 类和结构体在创建实例时如何设置初始状态 (
init
方法)。 - 析构过程 (Deinitialization): 类实例在被销毁前如何执行清理工作 (
deinit
方法)。 - 继承 (Inheritance): (仅限类) 一个类如何获得另一个类 (父类) 的属性和方法。
- 类型转换 (Type Casting): 检查实例的类型 (
is
),或者将其向下转型 (as?
,as!
) 为其子类类型。 - 协议 (Protocols): 定义一套方法、属性或其他要求的蓝图,类、结构体或枚举可以遵循 (conform to) 这个协议来实现这些要求。类似其他语言的接口 (Interface)。
- 扩展 (Extensions): 为已有的类、结构体、枚举或协议类型添加新功能 (方法、计算属性、构造器、协议遵循等),即使你没有原始源代码。
- 错误处理 (Error Handling): 使用
throw
,throws
,do-catch
,try?
,try!
来处理程序中可能发生的错误情况。 - 并发 (Concurrency): 使用
async
/await
等现代 Swift 特性来编写异步代码,处理耗时任务而不会阻塞主线程。 - 泛型 (Generics): 编写灵活、可重用的函数和类型,它们可以适用于任何类型,同时保持类型安全。
- 内存管理 (Memory Management): Swift 使用自动引用计数 (ARC) 来管理内存。了解 ARC 的基本原理以及如何解决循环引用问题 (使用
weak
和unowned
引用) 对于编写高效且无内存泄漏的应用至关重要。
第十一章:下一步:构建应用
学习了 Swift 语言本身后,下一步就是将其应用于实际的应用程序开发。这通常涉及到学习 Apple 提供的 UI 框架:
- SwiftUI: Apple 最新的声明式 UI 框架,用于为所有 Apple 平台构建用户界面。它使用更现代、更简洁的 Swift 语法,并提供实时预览功能。对于新项目和初学者来说,这是一个很好的选择。
- UIKit: 成熟的、命令式的 UI 框架,长期用于构建 iOS 和 tvOS 应用。如果你需要维护旧项目或需要 UIKit 提供的某些特定功能,可能需要学习它。
- AppKit: 用于构建 macOS 应用程序的 UI 框架。
你需要学习如何使用这些框架来创建视图、布局元素、处理用户交互、导航、管理数据等。
结语:持续学习与实践
Swift 是一门仍在不断发展的语言,Apple 每年都会带来新的特性和改进。学习 Swift 是一个持续的过程。
- 多写代码: 理论学习固然重要,但只有通过不断编写代码、解决问题、尝试新特性,才能真正掌握它。利用 Playgrounds 进行实验,参与小型项目,逐步挑战更复杂的任务。
- 阅读官方文档: Apple 的 Swift 官方文档 (The Swift Programming Language Guide) 是最权威、最全面的学习资源。
- 参与社区: 加入 Swift 开发者论坛、Stack Overflow、关注 Swift相关的博客和 Twitter 账号,与其他开发者交流,学习他们的经验。
- 贡献开源: 如果你对 Swift 本身或其生态系统感兴趣,可以参与到开源项目中。
掌握 Swift 将为你打开通往 Apple 庞大且充满活力的生态系统的大门,无论是开发下一款热门 App,还是构建强大的桌面工具,或是探索其他应用领域,Swift 都将是你手中强大的利器。祝你在这段学习旅程中收获满满,享受编程的乐趣!