移动开发中的 Swift 特性:定义、关键功能及工作原理

作者: IT Sectr 发布日期: 2026-06-25 阅读时间: 17 分钟

Swift 是 Apple 的编程语言,结合了 Objective-C 的强大和现代语言的安全性。与许多其他语言不同,Swift 提供了独特的构造:值类型具有自动复制功能,Actors 具有编译器级别的隔离,Property Wrappers 用于重用属性逻辑,以及面向协议编程作为经典继承的替代方案。根据Apple Swift 文档,这些功能使代码更安全、更高效。

要点

  • Struct — 值类型,赋值时复制,不支持继承。Class — 引用类型,通过引用传递,支持继承和 deinit。
  • Actor — 线程安全的引用类型,具有编译器级别的隔离。编译器禁止不同任务同时访问可变状态。
  • Property Wrappers — @Published、@State、@Binding 在 SwiftUI 和 Combine 中封装属性逻辑,消除样板代码。
  • Generics 允许编写类型安全的函数和结构,而不绑定到特定类型。面向协议编程使用 extension + protocol 代替类层次结构。
  • Enum 带关联值 — 用于建模状态的强大工具。每个 case 可以存储不同类型数据的任意集合。

Swift 中的 Struct vs Class:值类型和引用类型

在 Swift 中,struct 是值类型,而 class 是引用类型。这种根本差异决定了赋值、传递给函数和内存管理期间的行为。Struct 在每次赋值时完全复制,class 通过引用传递 — 只复制指向堆对象的指针。

Struct 的值语义

Struct 自动实现值语义 — 每个实例都是独立的。Struct 属性的修改只能通过 var 实现,修改属性的方法必须标记为 mutating。Struct 不支持继承,但可以遵守协议。Swift 标准库对 String、Array、Dictionary、Int、Bool 使用 struct — 所有基本类型都是值类型。

swift
struct User {
    let id: Int
    var name: String

    mutating func updateName(_ newName: String) {
        name = newName
    }
}

var user1 = User(id: 1, name: "Alice")
var user2 = user1
user2.updateName("Bob")
// user1.name === "Alice",user2.name === "Bob"
// 完整复制 — 独立实例

Class 的引用语义

Class 存储在堆中并支持引用相等性。多个变量可以引用同一个对象,通过一个引用所做的更改在所有其他引用中可见。Class 支持继承,允许方法覆盖(override),具有用于资源清理的 deinit。在 Swift 中,class 的使用频率低于 struct — Apple 建议从 struct 开始,仅在需要继承或引用语义时才切换到 class。

swift
class Car {
    let model: String
    var speed: Int

    init(model: String, speed: Int) {
        self.model = model
        self.speed = speed
    }

    func accelerate(_ amount: Int) {
        speed += amount
    }
}

let car1 = Car(model: "Tesla", speed: 0)
let car2 = car1
car2.accelerate(50)
// car1.speed === 50,car2.speed === 50
// 同一引用 — 更改处处可见

struct 和 class 的选择取决于数据语义。对于配置、数据模型和状态,使用 struct — 复制更安全且更可预测。对于具有标识的对象(用户会话、数据库连接)以及需要继承时,使用 class。

Actor — Swift 中的线程安全

Actor 是一种引用类型,可以保护其可变状态免受并发访问。Swift 编译器保证 actor 变量只能从同一 actor 上下文中访问 — 任何外部访问都会导致编译错误。这在语言级别防止了数据竞争,无需手动锁定。

Actor 隔离

默认情况下,所有 actor 属性和方法都是隔离的。从外部调用 actor 方法需要 await — Swift 将执行切换到 actor 并返回。Actor 顺序同步访问:来自不同任务的请求排队。这保证了 actor 内部状态的一致性。非隔离方法(nonisolated)可以在没有 await 的情况下调用,但不能访问可变属性。

swift
actor BankAccount {
    private var balance: Double

    init(initialBalance: Double) {
        balance = initialBalance
    }

    func deposit(amount: Double) {
        balance += amount
    }

    func withdraw(amount: Double) throws {
        guard balance >= amount else {
            throw BankError.insufficientFunds
        }
        balance -= amount
    }

    nonisolated func accountInfo() -> String {
        return "银行账户"
    }
}

let account = BankAccount(initialBalance: 1000)
await account.deposit(amount: 500)
// balance = 1500 — 原子操作,无竞争条件

Actor 对于状态管理器、缓存和网络服务很有用,多个任务同时读写数据。与通过 NSLock 或 DispatchQueue 进行手动同步不同,actor 在编译时检查 — 不可能出错。隔离错误发生在编译时,而不是运行时。

Property Wrappers:@Published、@State、@Binding

Property Wrapper 是一种注解,可以在不更改声明的情况下向属性添加行为。SwiftUI 和 Combine 提供内置包装器来管理视图状态。编译器生成包装属性访问的代码,添加观察、同步或转换逻辑。

@Published 和 @State

@Published 用于遵守 ObservableObject 的类,以自动发布更改。Combine 订阅者在每次更新时收到通知。@State 是 SwiftUI 视图的本地状态。当 @State 更改时,SwiftUI 自动重绘视图。@Binding 在父视图和子视图之间创建双向连接,而不拥有数据。

swift
import SwiftUI
import Combine

@main
struct CounterApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

class CounterViewModel: ObservableObject {
    @Published var count: Int = 0

    func increment() {
        count += 1
    }
}

struct ContentView: View {
    @State private var greeting: String = "你好"
    @StateObject private var viewModel = CounterViewModel()

    var body: some View {
        VStack {
            CounterDisplay(count: $viewModel.count)
            Text(greeting)
            Button("增加", action: viewModel.increment)
        }
    }
}

struct CounterDisplay: View {
    @Binding var count: Int

    var body: some View {
        Text("计数:\(count)")
    }
}

自定义 Property Wrappers 使用 @propertyWrapper 和带有 wrappedValue 字段的结构创建。这允许重用逻辑:验证、规范化、UserDefaults 存储。Property Wrappers 显著减少了样板代码,并通过注解使开发人员的意图明确。

swift
@propertyWrapper
struct Clamped<T: Comparable> {
    private var value: T
    let range: ClosedRange<T>

    init(wrappedValue: T, range: ClosedRange<T>) {
        self.range = range
        self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
    }

    var wrappedValue: T {
        get { value }
        set { value = min(max(newValue, range.lowerBound), range.upperBound) }
    }
}

struct Settings {
    @Clamped(range: 0...100) var volume: Int = 50
}

var s = Settings()
s.volume = 150
// s.volume === 100 — 值自动受限

Generics 和面向协议编程

Generics 是 Swift 的一种强大机制,用于编写灵活而不会丢失类型安全的代码。泛型函数和类型适用于满足约束的任何类型。面向协议编程(POP)是一种 Swift 范式,其中 protocol + extension 取代了经典的类继承,提供了行为组合。

泛型约束和关联类型

Swift 允许使用协议(where 子句)约束泛型参数。带有关联类型的协议是协议的泛型等价物:具体类型在遵守时确定。这是类型安全集合和算法的基础。Swift 标准库积极使用 Generics:Array、Dictionary、Optional — 都是泛型类型。

swift
protocol Cacheable {
    associatedtype Key: Hashable
    associatedtype Value

    func get(_ key: Key) -> Value?
    mutating func set(_ value: Value, for key: Key)
}

struct MemoryCache<K: Hashable, V>: Cacheable {
    private var storage: [K: V] = [:]

    typealias Key = K
    typealias Value = V

    func get(_ key: K) -> V? {
        return storage[key]
    }

    mutating func set(_ value: V, for key: K) {
        storage[key] = value
    }
}

func firstMatch<T: Equatable>(in array: [T], target: T) -> Int? {
    for (index, item) in array.enumerated() {
        if item == target {
            return index
        }
    }
    return nil
}

let numbers = [10, 20, 30, 40]
let index = firstMatch(in: numbers, target: 30)
// index === 2 — 所有 Equatable 类型的通用方法

协议扩展和默认实现

协议扩展是 POP 的关键机制。在扩展中可以为协议方法提供默认实现。遵守协议的类型免费获得此实现,并可以在需要时覆盖它。这允许在不继承类的情况下构建行为层次结构 — 多重继承的替代方案,Swift 中不存在多重继承。

swift
protocol Drivable {
    var speed: Double { get set }
    func drive()
}

extension Drivable {
    func drive() {
        if speed > 0 {
            print("以 \(speed) 公里/小时行驶")
        }
    }
}

struct Bicycle: Drivable {
    var speed: Double = 15
}

struct Plane: Drivable {
    var speed: Double = 900

    func drive() {
        print("以 \(speed) 公里/小时飞行")
    }
}

let bike = Bicycle()
bike.drive() // "以 15.0 公里/小时行驶" — 默认

let plane = Plane()
plane.drive() // "以 900.0 公里/小时飞行" — 覆盖

使用扩展的 POP 允许通过多个协议进行组合 — 一个类型可以遵守多个协议,获取它们的实现。这比单一类继承更灵活,在类继承中整个层次结构是固定的。Swift 标准库构建在 POP 之上:CustomStringConvertible、Equatable、Hashable、Codable — 都是带有扩展的协议。

带关联值和 Extension 的 Enum

Enum 在 Swift 中是一个比其他语言强大得多的构造。每个 case 可以具有关联值 — 任意类型的数据的任意集合。结合扩展和模式匹配,enum 成为建模状态、操作结果和有限状态机的基础。

关联值和模式匹配

关联值允许每个 case 存储唯一数据。例如,loading case 不包含数据,success 包含结果,failure 包含错误。通过 switch 进行模式匹配提取关联值并处理每个 case。编译器检查穷尽性(exhaustive switch)— 这消除了被遗忘的状态。

swift
enum NetworkResult<T> {
    case loading
    case success(T)
    case failure(Error)
}

enum MediaFile {
    case image(url: URL, width: Int, height: Int)
    case video(url: URL, duration: Double)
    case audio(url: URL, bitrate: Int)
}

func handle(_ result: NetworkResult<String>) {
    switch result {
    case .loading:
        print("加载中...")
    case .success(let data):
        print("已接收:\(data)")
    case .failure(let error):
        print("错误:\(error.localizedDescription)")
    }
}

let file = MediaFile.video(url: URL(string: "https://example.com/video.mp4")!, duration: 120)

if case .video(let url, let duration) = file {
    print("视频 \(url) 时长 \(duration)秒")
}

Enum 的 Extension

Swift 允许通过 extension 为 enum 添加方法、计算属性和协议遵守。这不需要更改原始声明 — 代码保持干净和模块化。扩展中的方法可以处理关联值、计算派生数据并实现业务逻辑。

swift
extension MediaFile {
    var fileName: String {
        switch self {
        case .image(let url, _, _),
             .video(let url, _),
             .audio(let url, _):
            return url.lastPathComponent
        }
    }

    var description: String {
        switch self {
        case .image(_, let w, let h):
            return "图片 \(w)x\(h)"
        case .video(_, let d):
            return "视频 \(d)秒"
        case .audio(_, let b):
            return "音频 \(b) kbps"
        }
    }
}

extension MediaFile: Equatable {
    static func == (lhs: MediaFile, rhs: MediaFile) -> Bool {
        return lhs.fileName == rhs.fileName
    }
}

let files: [MediaFile] = [file]
files.forEach { print($0.description) }
// "视频 120.0s"

带关联值的 Enum + extension 是 Kotlin 中密封类或其他语言中判别联合的替代品。Swift 将此组合用于 Result(标准库中的标准类型)、Optional(Optional 是带有 .none 和 .some 的 enum)以及 SwiftUI 中的状态处理。模式匹配保证所有状态都已处理 — 运行时错误是不可能的。

常见问题解答

Swift 中 struct 和 class 有什么区别?

Struct — 值类型,赋值时复制,不支持继承。Class — 引用类型,通过引用传递,支持继承、deinit 和身份检查(===)。Apple 建议从 struct 开始,仅在需要引用语义时切换到 class。

Swift 中的 Actor 是什么以及何时使用?

Actor — 线程安全的引用类型。编译器禁止从不同上下文同时访问可变状态。对多个任务访问的状态管理器、缓存和服务使用 Actor。调用 actor 方法需要 await。

SwiftUI 中使用哪些 Property Wrappers?

主要:@State — 本地视图状态;@Binding — 不拥有数据的数据连接;@Published — 在 ObservableObject 中发布更改;@StateObject — 拥有 ObservableObject;@EnvironmentObject — 来自环境的依赖关系。自定义包装器通过 @propertyWrapper 创建。

什么是面向协议编程?

POP — 一种 paradigm,其中 protocol + extension 定义行为,而不是类继承。一个类型遵守多个协议,免费获取它们的实现。这是组合而非继承 — 更灵活、更安全。Swift 标准库(Equatable、Codable)构建在 POP 之上。

Swift 中的 enum 与其他语言中的 enum 有何不同?

Swift 中的 enum 支持关联值 — 每个 case 可以存储不同类型的数据。结合模式匹配(switch),为状态、错误和领域驱动设计提供了强大的模型。Optional 和 Result 是带有关联值的标准 Swift enum。

总结

  • Struct vs Class — 根本区别:值类型被复制,引用类型通过引用传递。Struct 不支持继承,class 支持。Apple 默认推荐 struct。
  • Actor — 编译器级别的线程安全。隔离的可变状态,通过 await 访问,无需手动锁定即可防止数据竞争。
  • Property Wrappers — @Published、@State、@Binding 以及通过 @propertyWrapper 自定义。减少样板代码,通过注解使意图明确。
  • Generics — 具有 where 约束的类型安全函数和类型。用于抽象的 Protocol + 关联类型。Swift 标准库完全构建在 generics 之上。
  • 面向协议编程 — 通过协议扩展进行行为组合。默认实现允许在不继承类的情况下重用代码。
  • 带关联值的 Enum — 用于状态的强大模型。每个 case 存储唯一数据。模式匹配保证所有状态都已处理。
  • Extension — 为任何类型(包括 enum)添加方法、计算属性和协议遵守。代码保持模块化和干净,无需更改原始声明。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目