Swift 是 Apple 的编程语言,结合了 Objective-C 的强大和现代语言的安全性。与许多其他语言不同,Swift 提供了独特的构造:值类型具有自动复制功能,Actors 具有编译器级别的隔离,Property Wrappers 用于重用属性逻辑,以及面向协议编程作为经典继承的替代方案。根据Apple Swift 文档,这些功能使代码更安全、更高效。
要点
在 Swift 中,struct 是值类型,而 class 是引用类型。这种根本差异决定了赋值、传递给函数和内存管理期间的行为。Struct 在每次赋值时完全复制,class 通过引用传递 — 只复制指向堆对象的指针。
Struct 自动实现值语义 — 每个实例都是独立的。Struct 属性的修改只能通过 var 实现,修改属性的方法必须标记为 mutating。Struct 不支持继承,但可以遵守协议。Swift 标准库对 String、Array、Dictionary、Int、Bool 使用 struct — 所有基本类型都是值类型。
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 支持继承,允许方法覆盖(override),具有用于资源清理的 deinit。在 Swift 中,class 的使用频率低于 struct — Apple 建议从 struct 开始,仅在需要继承或引用语义时才切换到 class。
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 变量只能从同一 actor 上下文中访问 — 任何外部访问都会导致编译错误。这在语言级别防止了数据竞争,无需手动锁定。
默认情况下,所有 actor 属性和方法都是隔离的。从外部调用 actor 方法需要 await — Swift 将执行切换到 actor 并返回。Actor 顺序同步访问:来自不同任务的请求排队。这保证了 actor 内部状态的一致性。非隔离方法(nonisolated)可以在没有 await 的情况下调用,但不能访问可变属性。
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 Wrapper 是一种注解,可以在不更改声明的情况下向属性添加行为。SwiftUI 和 Combine 提供内置包装器来管理视图状态。编译器生成包装属性访问的代码,添加观察、同步或转换逻辑。
@Published 用于遵守 ObservableObject 的类,以自动发布更改。Combine 订阅者在每次更新时收到通知。@State 是 SwiftUI 视图的本地状态。当 @State 更改时,SwiftUI 自动重绘视图。@Binding 在父视图和子视图之间创建双向连接,而不拥有数据。
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 显著减少了样板代码,并通过注解使开发人员的意图明确。
@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 是 Swift 的一种强大机制,用于编写灵活而不会丢失类型安全的代码。泛型函数和类型适用于满足约束的任何类型。面向协议编程(POP)是一种 Swift 范式,其中 protocol + extension 取代了经典的类继承,提供了行为组合。
Swift 允许使用协议(where 子句)约束泛型参数。带有关联类型的协议是协议的泛型等价物:具体类型在遵守时确定。这是类型安全集合和算法的基础。Swift 标准库积极使用 Generics:Array、Dictionary、Optional — 都是泛型类型。
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 中不存在多重继承。
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 — 都是带有扩展的协议。
Enum 在 Swift 中是一个比其他语言强大得多的构造。每个 case 可以具有关联值 — 任意类型的数据的任意集合。结合扩展和模式匹配,enum 成为建模状态、操作结果和有限状态机的基础。
关联值允许每个 case 存储唯一数据。例如,loading case 不包含数据,success 包含结果,failure 包含错误。通过 switch 进行模式匹配提取关联值并处理每个 case。编译器检查穷尽性(exhaustive switch)— 这消除了被遗忘的状态。
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)秒")
}
Swift 允许通过 extension 为 enum 添加方法、计算属性和协议遵守。这不需要更改原始声明 — 代码保持干净和模块化。扩展中的方法可以处理关联值、计算派生数据并实现业务逻辑。
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 中的状态处理。模式匹配保证所有状态都已处理 — 运行时错误是不可能的。
常见问题解答
Struct — 值类型,赋值时复制,不支持继承。Class — 引用类型,通过引用传递,支持继承、deinit 和身份检查(===)。Apple 建议从 struct 开始,仅在需要引用语义时切换到 class。
Actor — 线程安全的引用类型。编译器禁止从不同上下文同时访问可变状态。对多个任务访问的状态管理器、缓存和服务使用 Actor。调用 actor 方法需要 await。
主要:@State — 本地视图状态;@Binding — 不拥有数据的数据连接;@Published — 在 ObservableObject 中发布更改;@StateObject — 拥有 ObservableObject;@EnvironmentObject — 来自环境的依赖关系。自定义包装器通过 @propertyWrapper 创建。
POP — 一种 paradigm,其中 protocol + extension 定义行为,而不是类继承。一个类型遵守多个协议,免费获取它们的实现。这是组合而非继承 — 更灵活、更安全。Swift 标准库(Equatable、Codable)构建在 POP 之上。
Swift 中的 enum 支持关联值 — 每个 case 可以存储不同类型的数据。结合模式匹配(switch),为状态、错误和领域驱动设计提供了强大的模型。Optional 和 Result 是带有关联值的标准 Swift enum。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。