侧边栏壁纸
博主头像
一叶舟的秘密花园 博主等级

行动起来,活在当下

  • 累计撰写 37 篇文章
  • 累计创建 15 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

Swift进阶

Li
Li
2025-04-07 / 0 评论 / 0 点赞 / 31 阅读 / 0 字
温馨提示:
本文最后更新于2025-06-09,若内容或图片失效,请留言反馈。 八月长江万里晴,千帆一道带风轻

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.0

3. 方法(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)  // 8

4. 继承(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: 5

6. 构造方法(初始化器)

构造方法用于在对象创建时初始化其属性。

默认构造方法

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: 0

2. 创建一个 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)   // banana

2. 创建一个泛型队列(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)                  // 输出:1

2. 扩展(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, SWIFT

2. 使用 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 提供五种访问级别,从最开放到最封闭如下:

访问级别

可访问范围

open

可以被模块外访问和继承/重写只用于类

public

可以被模块外访问,但不能继承/重写

internal

默认级别,只能在当前模块中访问

fileprivate

只能在当前文件中访问

private

只能在当前声明作用域(如类或结构体内部)访问

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

✅ 访问控制说明:

成员

修饰符

可访问范围

accountNumber

public

模块外部可读写

balance

private

仅类内可访问

deposit(amount:)

internal(默认)

当前模块可用

🧪 示例使用(模块内):

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

❗️对比不同修饰符:

修饰符

同文件内

同模块(不同文件)

外部模块

private

✅ 自类内

fileprivate

✅ 同文件

internal

public

✅(仅读)

open

✅(可继承/重写)

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

主线程(UI更新必须在这里)

DispatchQueue.global()

全局并发队列(后台任务)

DispatchQueue(label:)

自定义串行队列

✨ 延时执行

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 是一种方法,用于对视图进行“修饰”,比如加颜色、加圆角、加边框、设置字体等。

常见修饰符

修饰符

功能

.padding()

添加内边距

.background()

添加背景

.foregroundColor()

设置文字或图标颜色

.font()

设置字体大小和样式

.cornerRadius()

设置圆角

.frame()

设置宽高

.border()

添加边框

.shadow()

添加阴影

✨ 示例:多个修饰符组合使用

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(...) 提供

• 多个子视图都可以使用这个共享对象

🧠 状态管理小结:

属性

用途

适用场景

@State

管理本地状态

简单状态

@Binding

绑定父视图的状态

父子传值

@ObservedObject

跨多个视图观察对象状态

MVVM 模型数据

@EnvironmentObject

全局共享状态

App 级别数据共享

✅ 小练习

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,通过参数传入当前进度百分比。

0

评论区