1. 面向对象编程(OOP)
• 类(Class)和结构体(Struct)的区别
• 属性(存储属性、计算属性、属性观察器)
• 方法(实例方法、类型方法)
• 继承、多态、协议(Protocol)
• 构造方法(初始化器、可失败初始化)
2. Swift 高级特性
• 泛型(Generics)
• 扩展(Extensions)
• 闭包(Closures)和高阶函数(map、filter、reduce)
• 自动引用计数(ARC)与内存管理
• 访问控制(public、private、fileprivate、internal)
3. Swift 并发编程
• GCD(Grand Central Dispatch)
• Async/Await(Swift 5.5 之后的现代并发)
• Actor(防止数据竞争)
4. SwiftUI
• 视图(View)和修饰符(Modifiers)
• 状态管理(@State、@Binding、@EnvironmentObject、@ObservedObject)
• 动画与过渡效果
• 组合式 UI 设计(组件化)
5. 实战项目
• 用面向对象思想优化你的学生管理系统
• 继续开发 Xcode 俄罗斯方块小游戏,并加入 SwiftUI 界面
1. 面向对象编程(OOP)
1. 类(Class)和结构体(Struct)的区别
Swift 中,类和结构体的主要区别:
• 类 是引用类型(Reference Type),即多个变量可以引用同一个对象。
• 结构体 是值类型(Value Type),即每次赋值都会创建一个副本。
类(Class)示例
class Student {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
func introduce() {
print("Hi, my name is \(name) and I am \(age) years old.")
}
}
let student1 = Student(name: "Alice", age: 20)
student1.introduce()结构体(Struct)示例
struct StudentStruct {
var name: String
var age: Int
func introduce() {
print("Hi, my name is \(name) and I am \(age) years old.")
}
}
var student2 = StudentStruct(name: "Bob", age: 22)
student2.introduce()2. 属性(Properties)
Swift 的类和结构体可以有两种主要的属性:
• 存储属性(Stored Properties):存储具体值的变量或常量。
• 计算属性(Computed Properties):根据计算逻辑返回值。
存储属性
class Rectangle {
var width: Double
var height: Double
init(width: Double, height: Double) {
self.width = width
self.height = height
}
}计算属性
class Rectangle {
var width: Double
var height: Double
init(width: Double, height: Double) {
self.width = width
self.height = height
}
var area: Double {
return width * height
}
}
let rect = Rectangle(width: 5, height: 10)
print("Area: \(rect.area)") // 50.03. 方法(Methods)
方法可以是 实例方法 或 类型方法。
实例方法
class Car {
var speed: Int = 0
func accelerate() {
speed += 10
print("Current speed: \(speed)")
}
}
let car = Car()
car.accelerate() // Current speed: 10类型方法
class MathUtil {
static func add(a: Int, b: Int) -> Int {
return a + b
}
}
let sum = MathUtil.add(a: 5, b: 3)
print(sum) // 84. 继承(Inheritance)
Swift 支持单继承(一个类只能继承一个父类),但可以通过协议(Protocol)实现多态。
class Animal {
var name: String
init(name: String) {
self.name = name
}
func makeSound() {
print("Some generic animal sound")
}
}
class Dog: Animal {
override func makeSound() {
print("Woof! Woof!")
}
}
let myDog = Dog(name: "Buddy")
myDog.makeSound() // Woof! Woof!5. 协议(Protocol)
协议类似于 Java 或 C++ 中的接口,它定义了一组必须实现的方法或属性。
protocol Vehicle {
var speed: Int { get set }
func accelerate()
}
class Bicycle: Vehicle {
var speed = 0
func accelerate() {
speed += 5
print("Bicycle speed: \(speed)")
}
}
let bike = Bicycle()
bike.accelerate() // Bicycle speed: 56. 构造方法(初始化器)
构造方法用于在对象创建时初始化其属性。
默认构造方法
class Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}可失败构造方法(Failable Initializer)
class Temperature {
var celsius: Double
init?(celsius: Double) {
if celsius < -273.15 {
return nil // 失败返回 nil
}
self.celsius = celsius
}
}
if let temp = Temperature(celsius: -300) {
print("Valid temperature: \(temp.celsius)")
} else {
print("Invalid temperature!") // 输出 Invalid temperature!
}练习
1. 创建一个 Person 类,包含 name、age 属性,并实现 introduce() 方法,打印姓名和年龄。
class Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
func introduce(){
print("my name is \(name) and I am \(age) years old.")
}
}
let Person1 = Person(name: "Alice", age: 20)
Person1.introduce()
// my name is Alice and I am 20 years old.
// Program ended with exit code: 02. 创建一个 Vehicle 协议,要求包含 speed 属性和 move() 方法。然后创建一个 Car 类,实现该协议,并在 move() 方法中打印当前速度。
// 定义 Vehicle 协议
protocol Vehicle {
var speed: Int { get set } // 要求有一个 speed 属性
func move() // 要求有一个 move() 方法
}
// 实现 Vehicle 协议的 Car 类
class Car: Vehicle {
var speed: Int
init(speed: Int) {
self.speed = speed
}
func move() {
print("Car is moving at \(speed) km/h.")
}
}
// 测试代码
let myCar = Car(speed: 80)
myCar.move() // 输出:Car is moving at 80 km/h.2. Swift 高级特性
1. 泛型(Generics)
泛型可以让代码更加灵活和可复用,它允许我们编写可以适用于任意数据类型的代码,而不必为每种类型编写单独的实现。
1.1 泛型函数
普通函数通常只能处理特定类型,而 泛型函数 可以处理不同类型的数据。
示例:交换两个变量
func swapValues<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}
var num1 = 10, num2 = 20
swapValues(&num1, &num2)
print(num1, num2) // 输出: 20 10
var str1 = "Hello", str2 = "Swift"
swapValues(&str1, &str2)
print(str1, str2) // 输出: Swift Hello👉 T 是一个占位符,表示任意类型,Swift 在调用函数时会自动推断类型。
1.2 泛型类型
我们可以定义 泛型类 或 泛型结构体,以支持不同的数据类型。
示例:泛型栈(Stack)
struct Stack<T> {
private var elements: [T] = []
mutating func push(_ element: T) {
elements.append(element)
}
mutating func pop() -> T? {
return elements.popLast()
}
}
var intStack = Stack<Int>()
intStack.push(5)
intStack.push(10)
print(intStack.pop()!) // 输出: 10
var stringStack = Stack<String>()
stringStack.push("Swift")
stringStack.push("Generics")
print(stringStack.pop()!) // 输出: Generics👉 Stack<T> 是一个泛型结构体,T 代表栈中存储的数据类型。
1.3 泛型与协议(Protocol)结合
Swift 允许泛型类型遵守协议,这样可以让泛型更加灵活。
示例:限定泛型类型
protocol Printable {
func description() -> String
}
struct Person: Printable {
var name: String
func description() -> String {
return "Person: \(name)"
}
}
// 泛型函数,要求 T 遵循 Printable 协议
func printDescription<T: Printable>(_ item: T) {
print(item.description())
}
let p = Person(name: "Alice")
printDescription(p) // 输出: Person: Alice👉 T: Printable 表示 T 必须符合 Printable 协议,否则无法调用 printDescription。
练习
1. 创建一个泛型函数,接受两个参数并返回它们的最大值。
// 定义一个泛型函数,用于返回两个值中的最大值
func maxValue<T: Comparable>(_ a: T, _ b: T) -> T {
return a > b ? a : b
}
// 测试代码
let maxInt = maxValue(10, 20) // 返回 20
let maxDouble = maxValue(3.14, 2.71) // 返回 3.14
let maxString = maxValue("apple", "banana") // 返回 "banana"
print(maxInt) // 20
print(maxDouble) // 3.14
print(maxString) // banana2. 创建一个泛型队列(Queue)数据结构,实现 enqueue() 和 dequeue() 方法。
// 定义泛型队列结构体
struct Queue<T> {
private var elements: [T] = []
// 入队:将元素添加到队尾
mutating func enqueue(_ element: T) {
elements.append(element)
}
// 出队:移除并返回队首元素
mutating func dequeue() -> T? {
return elements.isEmpty ? nil : elements.removeFirst()
}
// 查看队列是否为空
var isEmpty: Bool {
return elements.isEmpty
}
// 返回队列中的元素数量
var count: Int {
return elements.count
}
// 查看队首元素但不移除
var front: T? {
return elements.first
}
}
// 测试代码
var intQueue = Queue<Int>()
intQueue.enqueue(10)
intQueue.enqueue(20)
intQueue.enqueue(30)
print(intQueue.dequeue() ?? "队列为空") // 输出:10
print(intQueue.dequeue() ?? "队列为空") // 输出:20
print(intQueue.count) // 输出:12. 扩展(Extensions)
扩展允许你向 现有的类、结构体、枚举 或 协议 添加新功能,而无需修改原始代码。这是 Swift 强大的一部分,让我们可以更灵活地组织代码。
2.1 基础扩展
可以使用 extension 关键字为已有类型添加方法或计算属性。
示例:为 Int 添加平方计算
extension Int {
var squared: Int {
return self * self
}
}
let num = 5
print(num.squared) // 输出: 25👉 squared 是一个 计算属性,扩展了 Int 类型。
2.2 扩展方法
扩展可以为现有类型添加新的实例方法。
示例:为 Double 添加转换为字符串的方法
extension Double {
func toString() -> String {
return String(format: "%.2f", self)
}
}
let pi = 3.14159
print(pi.toString()) // 输出: 3.14👉 这样,我们可以直接调用 toString(),提高代码的可读性。
2.3 扩展构造方法
扩展可以为结构体和类添加新的初始化方法。
示例:为 Rectangle 结构体添加一个新的初始化方法
struct Rectangle {
var width: Double
var height: Double
}
extension Rectangle {
init(side: Double) {
self.width = side
self.height = side
}
}
let square = Rectangle(side: 5)
print(square.width, square.height) // 输出: 5.0 5.0👉 这里我们给 Rectangle 添加了一个 init(side:) 构造方法,用于创建正方形。
2.4 扩展协议
扩展可以为协议提供默认实现,让所有遵循该协议的类型都能使用这些方法。
示例:扩展协议
protocol Describable {
func describe() -> String
}
extension Describable {
func describe() -> String {
return "This is a describable object."
}
}
struct Person: Describable {
var name: String
}
let p = Person(name: "Alice")
print(p.describe()) // 输出: This is a describable object.👉 Describable 协议本身没有提供 describe() 实现,但我们通过扩展提供了默认实现。
练习
1. 为 String 添加一个 reversedText 计算属性,返回字符串的倒序版本。
// 扩展 String,添加 reversedText 计算属性
extension String {
var reversedText: String {
return String(self.reversed())
}
}
// 测试代码
let original = "Hello, Swift!"
let reversed = original.reversedText
print(reversed) // 输出:!tfiwS ,olleH• self.reversed() 会返回一个 ReversedCollection<String> 类型,需要用 String(...) 构造函数将其转换回 String。
• 使用扩展(extension)不会修改原始类型,而是优雅地为其“添加功能”。
2. 为 Array 添加一个方法 unique(),返回去重后的数组。
// 扩展 Array,添加 unique() 方法
extension Array where Element: Hashable {
func unique() -> [Element] {
var seen: Set<Element> = []
return self.filter { element in
if seen.contains(element) {
return false
} else {
seen.insert(element)
return true
}
}
}
}
// 测试代码
let numbers = [1, 2, 3, 2, 1, 4, 5, 3]
let uniqueNumbers = numbers.unique()
print(uniqueNumbers) // 输出:[1, 2, 3, 4, 5]3. 闭包(Closures)
很好!现在我们进入 Swift 非常重要的一部分 —— 闭包(Closures)
闭包是 自包含的代码块,可以在代码中被传递和使用。它本质上是可以捕获上下文变量的 匿名函数,类似于其他语言中的 lambda 表达式。
3.1 闭包的基本语法
let greeting = {
print("Hello, Swift!")
}
greeting() // 输出: Hello, Swift!这是一个最基础的闭包,它没有参数也没有返回值。
3.2 带参数和返回值的闭包
let add: (Int, Int) -> Int = { (a, b) in
return a + b
}
let result = add(3, 4)
print(result) // 输出: 7• (Int, Int) -> Int 是闭包的类型。
• { (a, b) in ... } 是闭包的实现。
3.3 闭包简化写法
Swift 允许我们一步步简化闭包的语法:
let numbers = [1, 2, 3, 4, 5]
// 普通写法
let doubled1 = numbers.map({ (num: Int) -> Int in
return num * 2
})
// 类型推断
let doubled2 = numbers.map({ num in
num * 2
})
// 参数缩写
let doubled3 = numbers.map({ $0 * 2 })
// 尾随闭包(Trailing Closure)
let doubled4 = numbers.map { $0 * 2 }
print(doubled4) // [2, 4, 6, 8, 10]3.4 闭包捕获值
闭包可以“捕获”其定义时作用域内的变量,即使变量作用域已经结束。
func makeCounter() -> () -> Int {
var count = 0
return {
count += 1
return count
}
}
let counter = makeCounter()
print(counter()) // 输出: 1
print(counter()) // 输出: 2
print(counter()) // 输出: 3闭包捕获并保存了 count 的值,这使得 counter() 每次调用都能更新和记住状态。
3.5 闭包在高阶函数中的应用
Swift 中的许多集合方法都使用闭包,比如 map、filter、reduce:
let nums = [1, 2, 3, 4, 5]
let evenNums = nums.filter { $0 % 2 == 0 }
print(evenNums) // [2, 4]
let sum = nums.reduce(0) { $0 + $1 }
print(sum) // 15练习题
1. 写一个闭包,它接受一个 String,返回大写版本。
let toUppercase: (String) -> String = { input in
return input.uppercased()
}
// 测试代码
let original = "hello, swift"
let upper = toUppercase(original)
print(upper) // 输出:HELLO, SWIFT2. 使用 filter 和闭包,筛选出数组中大于 10 的元素。
let numbers = [3, 11, 7, 15, 9, 20, 1]
// 使用 filter 和闭包筛选出大于 10 的元素
let filtered = numbers.filter { $0 > 10 }
print(filtered) // 输出:[11, 15, 20]3. 写一个返回累加器的函数(闭包),每次调用累加 2(类似 makeCounter())。
// 返回一个闭包的函数,每次调用闭包会累加 2
func makeCounter() -> () -> Int {
var total = 0
return {
total += 2
return total
}
}
// 测试代码
let counter1 = makeCounter()
print(counter1()) // 输出:2
print(counter1()) // 输出:4
print(counter1()) // 输出:6
let counter2 = makeCounter()
print(counter2()) // 输出:2 (是另一个独立的累加器)4. 内存管理与 ARC
Swift 使用 自动引用计数(ARC) 来管理内存。每当你创建一个类的实例时,ARC 会自动追踪它的引用次数,并在没有强引用时自动释放内存。
4.1 强引用(Strong Reference)
默认情况下,变量对实例的引用是 强引用,引用计数 +1:
class Person {
let name: String
init(name: String) { self.name = name }
deinit { print("\(name) 被释放了") }
}
var person1: Person? = Person(name: "Alice")
var person2 = person1
person1 = nil // 不会释放,person2 还引用着它
person2 = nil // 此时引用计数为 0,释放对象4.2 强引用循环(Retain Cycle)
当两个实例互相强引用,就会造成内存无法释放的 引用循环。
class Teacher {
var student: Student?
deinit { print("Teacher 被释放") }
}
class Student {
var teacher: Teacher?
deinit { print("Student 被释放") }
}
var teacher: Teacher? = Teacher()
var student: Student? = Student()
teacher?.student = student
student?.teacher = teacher
teacher = nil
student = nil // ❌ 不会释放,互相强引用4.3 弱引用(weak)与无主引用(unowned)
✅ 解决办法:使用 weak 或 unowned 解决循环引用。
• weak:用于可选类型,引用对象可以为 nil。
• unowned:用于非可选类型,引用的对象必须始终存在。
class Teacher {
var student: Student?
deinit { print("Teacher 被释放") }
}
class Student {
weak var teacher: Teacher? // ✅ 用 weak 解决循环引用
deinit { print("Student 被释放") }
}4.4 闭包中的引用循环
闭包也会捕获并强引用 self,如果不小心会导致循环引用。
❌ 错误示例
class Counter {
var count = 0
var increment: (() -> Void)?
func start() {
increment = {
self.count += 1 // 捕获 self,造成循环引用
}
}
deinit { print("Counter 被释放") }
}✅ 正确写法:使用 捕获列表 [weak self] 或 [unowned self]
class Counter {
var count = 0
var increment: (() -> Void)?
func start() {
increment = { [weak self] in
self?.count += 1
}
}
deinit { print("Counter 被释放") }
}练习题
1. 创建两个类 Owner 和 Pet,模拟一对一关系,使用 weak 避免循环引用。
class Owner {
let name: String
var pet: Pet?
init(name: String) {
self.name = name
print("Owner \(name) 初始化")
}
deinit {
print("Owner \(name) 被释放")
}
}
class Pet {
let name: String
weak var owner: Owner? // 使用 weak 避免循环引用
init(name: String) {
self.name = name
print("Pet \(name) 初始化")
}
deinit {
print("Pet \(name) 被释放")
}
}
// 测试代码
do {
let owner = Owner(name: "Alice")
let pet = Pet(name: "Fluffy")
owner.pet = pet // Owner 强引用 Pet
pet.owner = owner // Pet 弱引用 Owner(避免循环引用)
}
// 作用域结束,owner 和 pet 都被释放2. 写一个类 TimerBox,它在初始化时创建一个闭包定时器,使用 [weak self] 避免循环引用。
import Foundation
class TimerBox {
var timer: Timer?
var counter = 0
init() {
print("TimerBox 初始化")
// 使用 weak self 避免循环引用
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
guard let self = self else {
print("self 已被释放")
return
}
self.counter += 1
print("计时器触发,第 \(self.counter) 次")
}
}
deinit {
timer?.invalidate()
print("TimerBox 被释放,计时器停止")
}
}
// 测试代码
var box: TimerBox? = TimerBox()
// 延迟 5 秒释放 TimerBox
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
box = nil
}5. 访问控制(Access Control)
访问控制用于限制代码中哪些属性、方法或类能被外部访问,确保模块间的封装性和安全性。
Swift 提供五种访问级别,从最开放到最封闭如下:
5.1 示例理解
public class PublicClass {
public var x = 0
internal var y = 1
private var z = 2
}
let obj = PublicClass()
print(obj.x) // ✅ 可以访问
// print(obj.y) // ❌ 如果不在当前模块,无法访问
// print(obj.z) // ❌ 私有成员无法访问5.2 open vs public(仅类和类成员)
• open: 可被其他模块继承和重写
• public: 可被访问,但不可继承/重写
open class OpenClass {
open func greet() {
print("Hello from open class")
}
}
public class PublicClass {
public func greet() {
print("Hello from public class")
}
}5.3 private 和 fileprivate
class MyClass {
private var a = 10
fileprivate var b = 20
}
extension MyClass {
func printValues() {
print(a) // ✅ private 在类内部可访问
print(b) // ✅ fileprivate 在同文件可访问
}
}但如果在另一个文件中使用:
// 不在同文件
let obj = MyClass()
// print(obj.a) ❌ private 无法访问
// print(obj.b) ❌ fileprivate 也无法访问5.4 访问控制修饰符的使用场景建议
• private: 用于封装类或结构体的内部细节
• fileprivate: 多个类型在一个文件中协作
• internal: 默认值,适合模块内共享(如 App 项目中)
• public: 提供给外部模块使用(但不可继承)
• open: 开放库或框架,允许外部继承
✅ 练习题
1. 定义一个类 BankAccount,它有 accountNumber(public)、balance(private)属性,和一个 deposit(amount:) 的方法(internal)。
class BankAccount {
// 公有属性:可从模块外访问
public var accountNumber: String
// 私有属性:只能在类内部访问
private var balance: Double
// 默认是 internal:同模块内可访问
init(accountNumber: String, initialBalance: Double) {
self.accountNumber = accountNumber
self.balance = initialBalance
}
// internal 方法:可在模块内访问
func deposit(amount: Double) {
guard amount > 0 else {
print("存款金额必须大于 0")
return
}
balance += amount
print("成功存入 \(amount),当前余额为 \(balance)")
}
// 额外方法:供测试时查看余额(仅类内访问)
private func printBalance() {
print("当前余额为:\(balance)")
}
}✅ 访问控制说明:
🧪 示例使用(模块内):
let myAccount = BankAccount(accountNumber: "1234567890", initialBalance: 1000)
myAccount.deposit(amount: 500) // ✅ 有效
print(myAccount.accountNumber) // ✅ 有效
// print(myAccount.balance) // ❌ 无法访问私有属性2. 创建两个类 User 和 Admin,其中 Admin 可以在同文件内修改 User 的私有数据,思考用哪种访问控制修饰符。
🧠 思路分析:
• 如果 User 的某些数据是私密的,但我们只允许 Admin 在同一个文件中访问它,不能让其他地方(即使是同模块)访问,就该用 fileprivate。
• fileprivate 允许 同一个源文件内的其他类、结构体、函数访问成员,比 private 放宽一些,但比 internal 仍有限制。
✅ 示例代码:
// User.swift(假设两个类写在同一个文件内)
class User {
var username: String
fileprivate var loginCount: Int // 只允许同文件中访问
init(username: String) {
self.username = username
self.loginCount = 0
}
func login() {
loginCount += 1
print("\(username) 登录了 \(loginCount) 次")
}
}
class Admin {
func resetLoginCount(for user: User) {
user.loginCount = 0 // ✅ 允许访问,因为在同一个文件内
print("管理员已重置 \(user.username) 的登录次数")
}
}🧪 测试代码(在同一个文件内):
let user = User(username: "Alice")
user.login()
user.login()
let admin = Admin()
admin.resetLoginCount(for: user)
user.login()❗️对比不同修饰符:
3. Swift 并发编程
✅ 我们将学习:
1. GCD(Grand Central Dispatch) —— 老牌方案,低层级控制。
2. Async/Await(现代并发) —— Swift 5.5 引入,更简洁、安全。
3. Actor —— 用于保护共享状态,避免数据竞争。
1. GCD(Grand Central Dispatch)
GCD 是苹果提供的 C 级 API,用于在后台线程异步处理任务。通过它,我们可以轻松地将耗时操作丢到后台,不阻塞主线程。
基本语法:DispatchQueue
// 异步执行在全局队列(后台线程)
DispatchQueue.global().async {
// 耗时操作
print("后台执行")
// 回到主线程更新 UI
DispatchQueue.main.async {
print("回到主线程")
}
}🎯 常用队列
✨ 延时执行
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
print("2 秒后执行")
}⛓ 串行 vs 并发队列
• 串行队列:一次只执行一个任务(任务排队)
• 并发队列:可以并发执行多个任务(任务并行)
✅ GCD 小练习:
1. 使用 DispatchQueue.global() 模拟一个后台下载,再切换回主线程更新“下载完成”消息。
使用 DispatchQueue.global() 来模拟后台下载,并使用 DispatchQueue.main.async 切换回主线程更新 UI。
import Foundation
func simulateDownload() {
// 在后台线程模拟下载任务
DispatchQueue.global().async {
print("下载任务开始...")
// 模拟一个下载延时(例如 3 秒)
sleep(3)
// 下载完成后切换回主线程更新 UI
DispatchQueue.main.async {
print("下载完成!")
}
}
}
// 测试调用
simulateDownload()
// 保持主线程运行,以便后台任务有时间执行(在 Playground 或 CLI 中需要这样做)
RunLoop.main.run()🧠 说明:
• DispatchQueue.global().async 在后台线程异步执行下载任务。
• sleep(3) 用来模拟下载过程中的延时。
• 下载完成后,DispatchQueue.main.async 切换回主线程更新消息(通常在 iOS 中更新 UI)。
• RunLoop.main.run() 用于在 Playground 或 CLI 环境中保持主线程运行,确保后台任务能够完成。
这个模拟可以用于实际开发中,处理异步操作(如网络请求)时,确保 UI 更新在主线程执行。
2. 写一个函数,延迟 3 秒后输出当前时间。
使用 DispatchQueue.main.asyncAfter 来实现延迟执行任务。下面是一个简单的示例函数,延迟 3 秒后输出当前时间:
import Foundation
func delayedPrintCurrentTime() {
// 延迟 3 秒后执行
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
let currentTime = Date()
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .medium
let timeString = formatter.string(from: currentTime)
print("当前时间:\(timeString)")
}
}
// 测试调用
delayedPrintCurrentTime()
// 保持主线程运行,以便延迟任务能够执行(在 Playground 或 CLI 中需要这样做)
RunLoop.main.run()🧠 说明:
• DispatchQueue.main.asyncAfter(deadline: .now() + 3) 设置延迟 3 秒后执行任务。
• DateFormatter 格式化当前时间,输出的时间格式是日期和时间(例如 Apr 10, 2025 at 10:05:00 AM)。
• RunLoop.main.run() 在 Playground 或 CLI 环境中保持主线程运行,确保延迟任务能够执行。
2. Async/Await
Async/Await 是一种更现代、简洁的方式来处理异步任务。它使得异步编程看起来像同步编程,从而大大简化了代码的可读性和维护性。
2.1 async 和 await 基本用法
• async:表示一个函数是异步的,可能会暂停执行,返回一个“将来完成”的值。
• await:用于等待异步函数完成并获取结果。
示例:异步函数的定义和调用
// 定义一个异步函数
func fetchData() async -> String {
// 模拟网络请求
await Task.sleep(2 * 1_000_000_000) // 模拟延时 2 秒
return "数据加载完成"
}
// 调用异步函数
Task {
let result = await fetchData()
print(result)
}这里的 Task 用于启动一个异步任务。await 会暂停代码的执行,直到 fetchData() 完成并返回结果。
2.2 async 函数与 await 异步调用
你可以将函数标记为 async,表示它执行的是异步操作。异步函数调用时,必须使用 await 等待结果。
示例:多个异步操作
func downloadFile() async -> String {
await Task.sleep(2 * 1_000_000_000) // 模拟下载过程
return "文件下载完成"
}
func processFile() async -> String {
await Task.sleep(1 * 1_000_000_000) // 模拟处理过程
return "文件处理完成"
}
Task {
async let file = downloadFile() // 开始下载
async let processed = processFile() // 同时开始处理
// 等待并获取结果
let downloadResult = await file
let processResult = await processed
print(downloadResult) // 输出: 文件下载完成
print(processResult) // 输出: 文件处理完成
}async let 用于并发启动多个异步任务,让它们并行执行,而不是依次执行。
2.3 错误处理与异步函数
异步函数支持使用 throw 抛出错误,调用时可以使用 do-catch 处理错误。
示例:带有错误处理的异步函数
enum DownloadError: Error {
case networkIssue
}
func downloadFile() async throws -> String {
// 模拟可能出错的操作
throw DownloadError.networkIssue
}
Task {
do {
let result = try await downloadFile()
print(result)
} catch {
print("发生错误: \(error)")
}
}✅ 练习题
1. 使用 async 和 await 模拟一个异步的数据请求函数,返回假数据并打印。
利用 Swift 5.5 的 async/await 语法来模拟一个异步的数据请求函数。下面是一个例子,模拟从网络获取数据,并在 2 秒后返回假数据,然后打印出来:
import Foundation
// 模拟异步数据请求函数
func fetchData() async -> String {
// 模拟网络延时
await Task.sleep(2 * 1_000_000_000) // 2 秒
return "假数据:用户信息"
}
// 使用 async/await 调用异步函数
func simulateDataRequest() async {
print("请求数据中...")
let data = await fetchData() // 异步等待 fetchData 完成
print("获取的数据:\(data)")
}
// 测试调用
Task {
await simulateDataRequest() // 使用 Task 启动异步任务
}
// 保持主线程运行以便异步任务完成
RunLoop.main.run()🧠 说明:
1. fetchData() 是一个异步函数,模拟了一个 2 秒的网络请求,返回一条假数据。
2. await Task.sleep() 模拟网络延迟,1_000_000_000 代表 1 秒,2 * 1_000_000_000 即 2 秒。
3. simulateDataRequest() 是另一个异步函数,调用 fetchData() 并在数据返回后打印。
4. 在主线程中调用 Task 来启动异步操作。
5. RunLoop.main.run() 用于保持主线程运行,确保异步任务完成。
输出:
请求数据中...
获取的数据:假数据:用户信息2. 编写一个带有错误处理的异步函数,它模拟一个下载任务并可能抛出错误。
创建一个异步函数,它模拟一个下载任务,并且可能在过程中抛出错误。这个错误可以是一个简单的枚举类型,例如网络连接失败或下载文件损坏等。我们将使用 async 和 await,并结合 throws 来处理错误。
代码示例:
import Foundation
// 定义可能出现的错误类型
enum DownloadError: Error {
case networkFailure
case invalidData
}
// 模拟一个异步下载任务,可能抛出错误
func downloadFile(from url: String) async throws -> String {
// 模拟网络请求的延迟
await Task.sleep(2 * 1_000_000_000) // 2秒延时
// 随机模拟下载是否成功,50% 的概率抛出错误
let success = Bool.random()
if success {
return "文件下载成功:\(url)"
} else {
// 随机错误类型
let errorType = Bool.random()
if errorType {
throw DownloadError.networkFailure
} else {
throw DownloadError.invalidData
}
}
}
// 使用 async/await 调用并处理错误
func simulateDownload() async {
let url = "https://example.com/file.zip"
do {
let result = try await downloadFile(from: url)
print(result) // 输出成功的下载信息
} catch DownloadError.networkFailure {
print("下载失败:网络连接问题")
} catch DownloadError.invalidData {
print("下载失败:数据无效")
} catch {
print("下载失败:未知错误")
}
}
// 测试调用
Task {
await simulateDownload()
}
// 保持主线程运行以便异步任务完成
RunLoop.main.run()🧠 说明:
1. DownloadError 枚举定义了两种可能的错误:networkFailure(网络问题)和 invalidData(数据无效)。
2. downloadFile(from:) 是一个带有错误处理的异步函数,它模拟了一个下载任务,随机决定是否成功。如果失败,抛出相应的错误。
3. 在 simulateDownload() 中,我们使用 do-catch 来处理可能抛出的错误,具体错误根据 DownloadError 进行分类处理。
4. Task.sleep 用于模拟下载延迟,await 和 try await 用于异步调用和错误处理。
输出:
如果下载成功:
文件下载成功:https://example.com/file.zip如果出现错误(例如网络问题):
下载失败:网络连接问题这种方式利用了 async/await 语法,让异步代码看起来像同步代码,更易于理解和维护,同时结合 throws 和 do-catch 语法处理错误,确保代码的健壮性。
3. Actor(防止数据竞争)
3.1 什么是 Actor?
Actor 是一种类型,它可以确保在多线程环境下只有一个线程可以访问它的 状态,从而避免数据竞争。它与类(class)类似,但提供了对其属性和方法的保护,确保这些操作不会与其他线程冲突。
3.2 actor 的基本语法
actor BankAccount {
private var balance: Double = 0.0
// 异步方法:用于获取余额
func getBalance() -> Double {
return balance
}
// 异步方法:用于存款
func deposit(amount: Double) {
balance += amount
}
// 异步方法:用于取款
func withdraw(amount: Double) {
balance -= amount
}
}3.3 访问 Actor 中的状态
因为 actor 确保了并发安全,所以我们必须通过 异步调用 来访问 actor 的状态。这是为了避免数据竞争。
示例:异步访问 Actor
let account = BankAccount()
// 异步存款和取款操作
Task {
await account.deposit(amount: 100)
await account.withdraw(amount: 50)
let balance = await account.getBalance()
print("当前余额:\(balance)") // 输出: 当前余额:50.0
}3.4 Actor 的锁机制与并发访问
Actor 通过自动使用锁来确保同一时间只有一个线程访问它的内部状态。这样,当我们有多个并发任务需要访问 actor 时,Swift 会自动保证安全。
示例:并发访问 Actor
actor DataStore {
private var data: [String] = []
func addData(_ value: String) {
data.append(value)
}
func fetchData() -> [String] {
return data
}
}
let store = DataStore()
// 并发任务访问 actor
Task {
async let task1 = store.addData("数据1")
async let task2 = store.addData("数据2")
await task1
await task2
let result = await store.fetchData()
print(result) // 输出: ["数据1", "数据2"]
}3.5 actor 与 async 的结合
actor 内部的方法都是异步的。你无法在同步方法中直接修改 actor 的状态,必须通过 await 来访问它的方法。
3.6 在并发环境下,为什么要使用 actor?
当你处理多个并发任务并需要访问共享数据时,传统的同步机制可能会变得复杂和容易出错。actor 通过保证每次只有一个线程访问共享数据,从而简化了代码并避免了线程安全问题。
✅ 练习题
1. 创建一个 actor 类型的 Counter,它有一个 count 属性,可以进行递增、递减操作,并返回当前值。
在 Swift 中,actor 是一种新的并发类型,旨在处理数据竞争和线程安全的问题。我们可以使用 actor 来创建一个线程安全的计数器类型,支持递增、递减操作,并能返回当前值。
下面是一个实现 Counter 的 actor 类型:
代码示例:
import Foundation
// 定义一个 actor 类型的 Counter
actor Counter {
private var count: Int = 0
// 递增操作
func increment() {
count += 1
}
// 递减操作
func decrement() {
count -= 1
}
// 获取当前值
func getCurrentValue() -> Int {
return count
}
}
// 测试代码
func simulateCounter() async {
let counter = Counter()
// 异步执行递增和递减操作
await counter.increment()
await counter.increment()
await counter.decrement()
// 获取当前计数值
let currentValue = await counter.getCurrentValue()
print("当前计数值:\(currentValue)") // 输出:1
}
// 启动异步任务
Task {
await simulateCounter()
}
// 保持主线程运行以便异步任务完成
RunLoop.main.run()🧠 说明:
1. actor Counter 是一个线程安全的类型,内部有一个私有的 count 属性,只有 Counter 的方法能够直接修改它。
2. increment() 和 decrement() 是操作 count 的方法,它们对 count 进行递增和递减。
3. getCurrentValue() 方法返回当前 count 的值。
4. 使用 await 来调用 actor 中的方法,因为 actor 内的操作是异步的,Swift 会确保线程安全。
为什么使用 actor?
actor 确保只有一个线程能够访问它的内部状态,避免了数据竞争和并发问题。比如,如果在多个线程上同时调用 increment() 或 decrement(),Swift 会自动处理这些并发访问,确保每次只有一个线程能执行这些操作。
2. 写一个 actor 类型的 Library,它管理一组书籍,提供借书和还书的方法。
好的!我们可以使用 actor 来创建一个 Library 类型,它管理一组书籍,并提供借书和还书的方法。借书时,书籍数量减少,还书时,书籍数量增加。
这里是一个简单的实现,模拟一个图书馆的借还书操作:
代码示例:
import Foundation
// 定义一个 Book 类型
struct Book {
let title: String
let author: String
}
// 定义一个 actor 类型的 Library
actor Library {
private var books: [Book]
init(books: [Book]) {
self.books = books
}
// 借书方法,成功返回借出的书籍,失败返回 nil
func borrowBook(title: String) -> Book? {
if let index = books.firstIndex(where: { $0.title == title }) {
let book = books.remove(at: index)
print("借出书籍: \(book.title)")
return book
} else {
print("没有找到这本书: \(title)")
return nil
}
}
// 还书方法,将书籍添加回图书馆
func returnBook(book: Book) {
books.append(book)
print("还回书籍: \(book.title)")
}
// 获取当前图书馆中的所有书籍
func getBooks() -> [Book] {
return books
}
}
// 测试代码
func simulateLibraryOperations() async {
// 创建一些书籍并初始化图书馆
let book1 = Book(title: "Swift 编程", author: "Apple")
let book2 = Book(title: "数据结构与算法", author: "John Doe")
let library = Library(books: [book1, book2])
// 借书
if let borrowedBook = await library.borrowBook(title: "Swift 编程") {
print("成功借到书籍: \(borrowedBook.title)")
}
// 获取当前书籍列表
let books = await library.getBooks()
print("当前书籍: \(books.map { $0.title })")
// 还书
await library.returnBook(book: book1)
// 获取还书后的书籍列表
let updatedBooks = await library.getBooks()
print("还书后的书籍: \(updatedBooks.map { $0.title })")
}
// 启动异步任务
Task {
await simulateLibraryOperations()
}
// 保持主线程运行以便异步任务完成
RunLoop.main.run()🧠 说明:
1. Book 类型:表示一本书,包含 title(书名)和 author(作者)两个属性。
2. Library actor:表示图书馆,内部管理书籍数组。它提供了以下方法:
• borrowBook(title:):借书方法,如果有这本书,借出并从图书馆中移除,否则返回 nil。
• returnBook(book:):还书方法,将书籍添加回图书馆。
• getBooks():获取当前图书馆的所有书籍。
3. simulateLibraryOperations():这个异步函数模拟了图书馆的借书、还书操作,并打印相应的信息。
4. 使用 await 调用 actor 中的方法,因为 actor 内部的操作是并发安全的,需要等待完成。
测试输出:
借出书籍: Swift 编程
成功借到书籍: Swift 编程
当前书籍: ["数据结构与算法"]
还回书籍: Swift 编程
还书后的书籍: ["数据结构与算法", "Swift 编程"]关键点:
• actor 类型在多线程环境中提供了线程安全性,避免数据竞争。
• await 用来调用异步的 actor 方法,确保操作的顺序和线程安全。
4. SwiftUI
1. SwiftUI 视图(View)和修饰符(Modifiers)
✅ 什么是 View?
在 SwiftUI 中,所有界面元素都是一个个视图(View),包括文字、图片、按钮、列表等等。每个 View 都是一个结构体(struct),并遵循 View 协议。
最简单的 View 示例:
struct MyView: View {
var body: some View {
Text("Hello, SwiftUI!")
}
}body 是必须实现的,它返回一个描述视图层级的 some View。
🛠 什么是 Modifier?
Modifier 是一种方法,用于对视图进行“修饰”,比如加颜色、加圆角、加边框、设置字体等。
常见修饰符
✨ 示例:多个修饰符组合使用
Text("Hello, SwiftUI!")
.font(.title)
.foregroundColor(.white)
.padding()
.background(Color.blue)
.cornerRadius(10)修饰符是链式调用的:每个修饰符会返回一个新的视图(不可变),你可以一个接一个地添加它们。
🔁 注意顺序的重要性!
修饰符的顺序会影响最终效果。例如:
// 加背景 → 再加圆角
Text("SwiftUI")
.padding()
.background(Color.yellow)
.cornerRadius(10)如果你把 .cornerRadius() 放在 .padding() 之前,效果会不同。因为你是对哪个视图加圆角(是文字本身,还是背景 + padding 的整体),结果不一样。
✅ 小练习
1. 写一个 Text("Hello"),让它有白色文字、蓝色背景、内边距、圆角和阴影。
2. 用 Image 显示一张图片,并设置为圆形(使用 .clipShape(Circle()))。
3. 创建一个包含三个 Text 的 VStack,每个文字都设置不同颜色和字体大小。
2. 状态管理(State Management)
SwiftUI 是一个响应式框架,也就是说,当你的数据发生变化时,界面会自动更新。为了实现这个功能,SwiftUI 提供了几种状态管理机制:
✅ 1. @State —— 最基础的状态管理
用于视图内部保存状态。当值变化时,界面自动刷新。
示例:计数器
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack {
Text("点击次数:\(count)")
.font(.title)
Button("点击我") {
count += 1
}
}
}
}每点击一次按钮,count 增加,Text 会自动更新。
✅ 2. @Binding —— 父视图和子视图共享状态
@Binding 用于子视图接收来自父视图的状态引用,允许修改父视图的数据。
示例:子视图控制父视图的值
struct ToggleView: View {
@Binding var isOn: Bool
var body: some View {
Toggle("开关", isOn: $isOn)
}
}
struct ParentView: View {
@State private var isOn = false
var body: some View {
VStack {
ToggleView(isOn: $isOn)
Text(isOn ? "开着" : "关着")
}
}
}• 父视图用 @State 管理状态
• 子视图用 @Binding 共享并修改它
✅ 3. @ObservedObject 和 ObservableObject —— 用于跨多个视图共享状态(推荐 MVC/MVVM 中使用)
你可以将数据封装到一个类中,多个视图观察这个类的状态变化。
示例:
class CounterModel: ObservableObject {
@Published var count = 0
}
struct CounterView: View {
@ObservedObject var model = CounterModel()
var body: some View {
VStack {
Text("点击次数:\(model.count)")
Button("加一") {
model.count += 1
}
}
}
}• @Published 表示该属性发生变化时通知视图更新
• @ObservedObject 表示当前视图监听这个对象的变化
✅ 4. @EnvironmentObject —— 全局共享状态
用来在多个视图之间共享一个全局对象,避免层层传递。
示例:
class AppSettings: ObservableObject {
@Published var themeColor: Color = .blue
}
struct ContentView: View {
var body: some View {
ChildView()
.environmentObject(AppSettings()) // 注入环境对象
}
}
struct ChildView: View {
@EnvironmentObject var settings: AppSettings
var body: some View {
Text("欢迎使用")
.foregroundColor(settings.themeColor)
}
}• @EnvironmentObject 由父视图通过 .environmentObject(...) 提供
• 多个子视图都可以使用这个共享对象
🧠 状态管理小结:
✅ 小练习
1. 创建一个 @State 开关控制背景颜色(蓝 or 红)。
2. 写一个父视图 + 子视图,子视图可以切换父视图的 Text 显示内容。
3. 使用 @ObservedObject 构建一个 Todo 列表,添加/删除任务时自动刷新。
3. 动画与过渡效果(Animations & Transitions)
SwiftUI 为动画提供了非常简洁且强大的方式,你几乎只需一行代码就能实现流畅的视觉效果。
✅ 1. 隐式动画(Implicit Animation)
只需在状态变化时添加 .animation(...) 修饰符,SwiftUI 就会自动为你补间变化。
示例:点击按钮放大或缩小
struct ScaleView: View {
@State private var isBig = false
var body: some View {
VStack {
Image(systemName: "star.fill")
.font(.system(size: isBig ? 100 : 50))
.foregroundColor(.yellow)
.animation(.easeInOut(duration: 0.5), value: isBig)
Button("切换大小") {
isBig.toggle()
}
}
}
}注意 SwiftUI 里动画是绑定在“某个值变化上”的,比如这里绑定的是 isBig。
✅ 2. 显式动画(withAnimation)
你也可以使用 withAnimation {} 包裹逻辑,使其只对这段变化应用动画。
Button("切换") {
withAnimation(.spring()) {
isBig.toggle()
}
}这样可以更明确地控制哪些内容需要动画,哪些不需要。
✅ 3. 过渡效果(Transition)
用于添加或移除视图时的动画效果,比如淡入淡出、滑入滑出等。
示例:点击按钮显示/隐藏视图
struct TransitionView: View {
@State private var show = false
var body: some View {
VStack {
Button("显示/隐藏") {
withAnimation {
show.toggle()
}
}
if show {
Text("Hello!")
.padding()
.background(Color.green)
.transition(.slide) // or .opacity, .scale
}
}
}
}常见过渡类型:
• .opacity:淡入淡出
• .slide:从一侧滑入
• .scale:缩放进出
• .move(edge: .leading):从特定方向移动
• .asymmetric(insertion: .scale, removal: .opacity):进入和退出使用不同效果
✅ 4. 动画曲线(Timing Curves)
SwiftUI 支持多种内建动画曲线:
• .linear
• .easeIn
• .easeOut
• .easeInOut
• .spring()(弹簧动画)
✅ 小练习
1. 点击按钮时改变图形的颜色和大小,添加动画效果。
2. 显示/隐藏一段文字,添加 .transition(.slide)。
3. 使用 .spring() 模拟弹性跳动效果。
4. 用 .animation 为一个 List 中添加新项时附带动画。
4. 组合式 UI 设计(组件化)
在 SwiftUI 中,我们鼓励使用“组件化”方式构建 UI:把复杂的界面拆分成小而独立的视图组件(Reusable Views),提高代码复用性和可维护性。
✅ 为什么要组件化?
• 可读性更好:每个组件关注自己的小功能。
• 可复用性更高:重复使用相同组件而不重复代码。
• 便于测试和调试:逻辑更清晰,易于定位问题。
✅ 1. 拆分视图组件
假设你有一个带头像、用户名、按钮的用户卡片:
struct UserCard: View {
var username: String
var body: some View {
HStack {
Image(systemName: "person.circle")
.resizable()
.frame(width: 40, height: 40)
Text(username)
.font(.headline)
Spacer()
Button("关注") {
print("关注按钮被点击")
}
}
.padding()
.background(Color(.secondarySystemBackground))
.cornerRadius(10)
}
}你可以在主界面中多次使用:
struct ContentView: View {
var body: some View {
VStack {
UserCard(username: "Alice")
UserCard(username: "Bob")
}
}
}✅ 2. 参数化组件
通过传参控制组件的行为和内容,提高灵活性。
struct CustomButton: View {
var title: String
var color: Color
var action: () -> Void
var body: some View {
Button(action: action) {
Text(title)
.padding()
.background(color)
.foregroundColor(.white)
.cornerRadius(8)
}
}
}使用方式:
CustomButton(title: "提交", color: .blue) {
print("提交按钮点击")
}✅ 3. 组件 + 状态组合(ViewModel 模式)
你可以将状态提取出来,通过 @Binding 或 @ObservedObject 注入组件,形成清晰的“视图 + 数据”结构。
struct Counter: View {
@Binding var value: Int
var body: some View {
HStack {
Button("-") { value -= 1 }
Text("\(value)")
Button("+") { value += 1 }
}
}
}
struct ContentView: View {
@State private var count = 0
var body: some View {
Counter(value: $count)
}
}✅ 4. 组件中的嵌套 View
组件也可以进一步拆分:
struct PostView: View {
let title: String
let content: String
var body: some View {
VStack(alignment: .leading) {
TitleView(title: title)
Text(content)
.font(.body)
}
.padding()
.background(Color(.systemGray6))
.cornerRadius(12)
}
}
struct TitleView: View {
let title: String
var body: some View {
Text(title)
.font(.title2)
.bold()
}
}✅ 小练习
1. 创建一个名为 ProfileCard 的组件,包含头像、用户名、简介。
2. 创建一个 LikeButton 组件,点击时切换图标并计数。
3. 创建一个组件 ProgressBar,通过参数传入当前进度百分比。
评论区