Swift — 结构体(Struct)

概念 #

在 Swift 中,结构体 struct 是一种非常有用的数据类型。它允许你将一组相关的值组合在一起,并定义它们的行为。结构体在 Swift 中是值类型,与类 class 的引用类型有显著的区别。

官方文档:Structures and Classes

结构体的定义 #

定义一个结构体使用 struct 关键字,结构体中可以包含属性(存储属性和计算属性)和方法。

struct Resolution {
    var width: Int
    var height: Int
}

在这个例子中,Resolution 结构体包含了两个存储属性 widthheight

创建结构体实例 #

你可以通过成员初始化器来创建结构体实例:

let res = Resolution(width: 1920, height: 1080)

在这个例子中,res 是一个新的 Resolution 实例,其 width 属性为 1920,height 属性为 1080。

访问和修改属性 #

可以通过点语法来访问和修改结构体的属性:

print("The resolution width is \(res.width)")

// 如果需要修改,需要将实例声明为 var
var modifiableRes = res
modifiableRes.width = 1280
print("The new resolution width is \(modifiableRes.width)")

方法 #

结构体可以包含方法,这些方法可以使用和修改结构体的属性。

struct Resolution {
    var width: Int
    var height: Int
    
    func display() {
        print("Resolution: \(width)x\(height)")
    }
    
    mutating func resize(toWidth width: Int, andHeight height: Int) {
        self.width = width
        self.height = height
    }
}

var res = Resolution(width: 1920, height: 1080)
res.display() // 输出: Resolution: 1920x1080
res.resize(toWidth: 1280, andHeight: 720)
res.display() // 输出: Resolution: 1280x720

在这个例子中,display() 方法用来打印分辨率,而 resize(toWidth:andHeight:) 方法是一个 mutating 方法,它可以修改结构体的属性。

构造器 #

当你定义一个结构体时,Swift 会自动提供一个成员初始化方法,你也可以自定义构造器:

struct Resolution {
    var width: Int
    var height: Int
    
    init(width: Int, height: Int) {
        self.width = width
        self.height = height
    }
}

值类型 #

结构体是值类型,这意味着结构体的实例在赋值和传递过程中会被拷贝:

let res1 = Resolution(width: 1920, height: 1080)
var res2 = res1
res2.width = 1280

print("res1 width: \(res1.width)") // 输出: res1 width: 1920
print("res2 width: \(res2.width)") // 输出: res2 width: 1280

在这个例子中,res1res2 是两个独立的实例,对 res2 的修改不会影响 res1

结构体与类的对比 #

结构体的特点:

  1. 值类型:结构体在赋值和传递时总是被拷贝。
  2. 不需要继承:结构体不支持继承。
  3. 自动成员初始化器:Swift 自动为结构体提供成员初始化器。
  4. 更适合表示简单的数据:例如几何图形、坐标、范围等。

类的特点:

  1. 引用类型:类在赋值和传递时总是引用同一个实例。
  2. 支持继承:类可以继承自其他类,并且可以使用多态。
  3. 需要手动定义初始化方法:类通常需要显式定义初始化方法。
  4. 更适合表示需要共享状态的数据:例如用户对象、单例模式等。

小结 #

结构体在 Swift 中是一种灵活强大的值类型,适用于表示简单的数据结构。它们使用方便,适合那些不需要继承和共享状态的情况。

  1. 定义结构体 通过 struct 关键字。
  2. 存储属性和计算属性:可以包含存储真实数据的属性,也可以包含计算属性。
  3. 方法:可以包含操作和修改结构体属性的方法。
  4. 值类型 赋值和传递时会被拷贝。
  5. 构造器 可以使用默认的成员初始化方法或自定义构造方法。

理解结构体和类之间的区别以及何时使用它们,对于开发高效、健壮的 Swift 应用至关重要。

使用实例 #

1. 定义一个简单的结构体 #

struct Rectangle {
    var width: Int
    var height: Int
    
    var area: Int {
        width * height
    }
    
    mutating func scale(by factor: Int) {
        width *= factor
        height *= factor
    }
}
var rect = Rectangle(width: 10, height: 5)
print(rect.area) // 输出:50
rect.scale(by: 2)
print(rect.area) // 输出:200

2. 属性观察器 #

struct Account {
    var balance: Int {
        willSet {
            print("About to set balance to \(newValue)")
        }
        didSet {
            print("Balance changed from \(oldValue) to \(balance)")
        }
    }
}
var account = Account(balance: 1000)
account.balance = 1200 // 触发 willSet 和 didSet

3. 嵌套 Struct #

struct Computer {
    struct Processor {
        var cores: Int
        var frequency: Double
    }
    var processor: Processor
}
let myComputer = Computer(processor: Computer.Processor(cores: 8, frequency: 3.5))
print("Processor cores: \(myComputer.processor.cores)")

4. 静态属性与方法 #

struct Student {
    static var totalStudents = 0
    
    var name: String
    
    init(name: String) {
        self.name = name
        Student.totalStudents += 1
    }
    
    static func printTotalStudents() {
        print("Total students: \(totalStudents)")
    }
}

let student1 = Student(name: "Alice")
let student2 = Student(name: "Bob")
Student.printTotalStudents() // 输出:Total students: 2

5. 可失败构造器 #

struct Person {
    var name: String
    var age: Int
    
    init?(name: String, age: Int) {
        if age < 0 {
            return nil // 返回 nil 表示构造失败
        }
        self.name = name
        self.age = age
    }
}

if let person = Person(name: "Alice", age: -1) {
    print("\(person.name) was created")
} else {
    print("Invalid age")
}

注意事项 #

  1. 值类型行为:

    • 结构体是值类型,当赋值或传递时,其实例会被复制。相较于类,它不会共享同一对象的引用。
    var rect1 = Rectangle(width: 10, height: 20)
    var rect2 = rect1
    rect2.width = 30
    print(rect1.width) // 输出:10(rect1 未受 rect2 修改影响)
    
  2. 静态成员:

    • 静态(static)属性或方法对所有实例共享。
    • 对于只需存储在类型本身的数据,可以用静态属性。
  3. 用作轻量建模:

    • 结构体最常用于封装轻量数据。对于复杂的行为或需要继承的场景,推荐使用类。
  4. 不可变结构体:

    • 如果结构体用 let 声明为常量,就无法修改它的属性,即使属性本身是 var 类型。

总结 #

常用特性: #

  1. 属性:

    • 存储属性:varlet
    • 计算属性:var
    • 属性观察器:willSetdidSet
  2. 方法:

    • 实例方法
    • 修改方法(需要 mutating 修饰)
    • 静态方法
  3. 初始化:

    • 默认初始化、自定义初始化、可失败初始化

适用场景: #

  • 封装一组轻量且相关的数据。
  • 创建不可变的数据结构。
  • 需要避免复杂引用和共享状态的场景。

默认参数 #

在 Swift 的 structfunc 中,默认参数 是指在函数或初始化方法(init)中给参数赋予默认值,使调用者可以省略该参数,而 Swift 会自动填充默认值。


🎯 默认参数的基本用法 #

在 Swift 中,我们可以为函数或结构体的初始化方法中的某些参数提供默认值。例如:

struct ButtonConfig {
    var title: String
    var color: String = "Blue" // 默认参数
}

// 可以省略 color 参数
let defaultButton = ButtonConfig(title: "Confirm")
print(defaultButton.color) // 输出 "Blue"

// 也可以自定义 color 参数
let redButton = ButtonConfig(title: "Cancel", color: "Red")
print(redButton.color) // 输出 "Red"

默认参数的作用

  • 让 API 更加简洁
  • 让调用者在不关心某些参数时可以省略它们
  • 使代码更具可读性

🛠 在 SwiftUI 视图修饰符中的默认参数 #

SwiftUI 的许多视图修饰符(如 confirmationDialog)都利用了默认参数,比如你提到的这个方法:

nonisolated func confirmationDialog<A>(
    _ titleKey: LocalizedStringKey,
    isPresented: Binding<Bool>,
    titleVisibility: Visibility = .automatic, // 默认参数
    @ViewBuilder actions: () -> A
) -> some View where A : View

✅ 这里 titleVisibility 参数有一个默认值 .automatic #

  • 如果调用者没有传递 titleVisibility,Swift 会自动使用 .automatic
  • 如果调用者提供了值,则使用传入的值

🚀 示例 #

struct ContentView: View {
    @State private var isDialogPresented = false

    var body: some View {
        Button("Show Dialog") {
            isDialogPresented = true
        }
        .confirmationDialog(
            "Are you sure?",
            isPresented: $isDialogPresented
        ) { // 这里省略了 `titleVisibility`
            Button("Yes", role: .destructive) {}
            Button("Cancel", role: .cancel) {}
        }

        .confirmationDialog(
            "Are you sure?",
            isPresented: $isDialogPresented,
            titleVisibility: .visible // 这里显式传递 `titleVisibility`
        ) {
            Button("Yes", role: .destructive) {}
            Button("Cancel", role: .cancel) {}
        }
    }
}

在第一个 .confirmationDialog 调用中,titleVisibility 没有提供,所以默认值 .automatic 生效。
在第二个调用中,显式提供了 .visible,所以 titleVisibility 的行为会改变。


🌟 结构体(struct)中的默认参数 #

默认参数不仅可以用于函数,也可以用于 structinit 方法。例如:

struct User {
    var name: String
    var age: Int = 18  // 默认参数
}

// 省略 `age` 参数,默认使用 18
let user1 = User(name: "Alice")
print(user1.age) // 输出 18

// 显式提供 `age`
let user2 = User(name: "Bob", age: 25)
print(user2.age) // 输出 25

User 结构体中,age 有一个默认值 18,所以在创建 User 实例时可以省略 age,Swift 会自动填充默认值。


🔥 结合泛型、默认参数和 @ViewBuilder #

在 SwiftUI 中,我们经常看到类似 confirmationDialog 这样复杂的函数,它们结合了:

  1. 泛型A: View)—— 允许不同的视图作为 actions
  2. 默认参数titleVisibility = .automatic
  3. @ViewBuilder —— 使 actions 允许传入多个视图

例如:

func customDialog<A: View>(
    title: String,
    isPresented: Binding<Bool>,
    titleVisibility: Visibility = .automatic, // 默认参数
    @ViewBuilder actions: () -> A
) -> some View {
    VStack {
        Text(title)
            .font(.headline)
        
        actions() // 这里会展开 `ViewBuilder` 生成的多个视图
    }
}

这样,我们可以写:

.customDialog(title: "Warning", isPresented: $isPresented) { 
    Button("OK") {} 
    Button("Cancel") {}
}

省略 titleVisibility,默认使用 .automatic


💡 结论 #

  1. 默认参数让 API 更简洁,调用者可以省略不关心的参数,提升可读性。
  2. SwiftUI 的许多视图修饰符使用默认参数,如 .confirmationDialog
  3. 结构体的 init 也可以使用默认参数,提供更友好的初始化方式。
  4. 默认参数可以与泛型、@ViewBuilder 结合使用,增强 SwiftUI 视图构造的灵活性。
本文共 2720 字,创建于 Nov 7, 2024
相关标签: Swift, Xcode