要构建健壮的现代 Swift 应用,你需要将 Swift 6.2 的 Approachable Concurrency 模型、SwiftUI 的 @Observable 与 NavigationStack、以及基于 Actor 的线程安全持久化层组合运用。这套实践能让你的 UI 高效刷新、数据层无竞争、并发控制显式化,并且通过协议依赖注入使整个架构易于测试和 Mock。

Swift 现代并发与 UI 架构:Actor 持久化、Approachable Concurrency 与 SwiftUI @Observable

现代 Swift 开发已从简单的 MVC 演进到一套完整的、以并发安全和声明式 UI 为核心的架构范式。面对复杂的异步数据流、多线程访问和不断增长的测试需求,开发者需要系统性地整合 Swift 语言的最新特性。本指南将串联四个关键的工程实践——基于 SwiftUI Patterns Skill@Observable 与 NavigationStack、基于 Swift Actor Persistence Skill 的线程安全持久化、基于 Swift Concurrency 6.2 Skill 的 Approachable Concurrency 模型,以及基于 Swift Protocol DI Testing Skill 的可测试架构,为你构建一个从 UI 到数据层都可靠、高性能且易于维护的 Swift 应用蓝图。

一、UI 层:@Observable 状态管理与类型安全导航

SwiftUI 的渲染性能与状态管理密不可分。@Observable 宏是 Observation 框架的核心,它取代了旧的 ObservableObject 协议与 @Published 属性包装器,实现了属性级别的变更追踪。SwiftUI 只会重新计算并刷新那些实际读取了被修改属性的视图,而非整个对象对应的视图树,这从根本上解决了大型页面因单个属性变化而全量重绘的性能问题。

@Observable
final class UserProfileViewModel {
    var username: String = ""
    var isLoading: Bool = false
    private(set) var avatarData: Data? = nil
    // ... 其他状态
}

在视图中,使用 @State 持有 ViewModel 实例,这是当前推荐的绑定方式:

struct UserProfileView: View {
    @State private var viewModel: UserProfileViewModel

    var body: some View {
        Form {
            if viewModel.isLoading {
                ProgressView()
            }
            TextField("Username", text: $viewModel.username)
            // ... 其他 UI 元素
        }
        .task { await viewModel.loadProfile() }
    }
}

导航方面,应摒弃字符串标识符和不安全的 Any 类型。使用 NavigationStack 结合 NavigationPath 和一个枚举 Destination,可以实现完全类型安全的、可编程的导航流程。

enum Destination: Hashable {
    case settings
    case postDetail(id: String)
    case friendProfile(userId: String)
}

@Observable
final class Router {
    var path = NavigationPath()
    func navigate(to destination: Destination) { path.append(destination) }
}

// 在根视图中使用
NavigationStack(path: $router.path) {
    HomeView()
        .navigationDestination(for: Destination.self) { destination in
            switch destination {
            case .settings: SettingsView()
            case .postDetail(let id): PostDetailView(id: id)
            case .friendProfile(let id): FriendProfileView(userId: id)
            }
        }
}

二、数据层:基于 Actor 的线程安全持久化

当应用需要本地持久化数据(如用户设置、缓存内容)时,如何保证多个视图或后台任务并发读写同一份数据时的安全?传统的 DispatchQueue 或锁方案容易出错。Actor 模型由 Swift 编译器强制保证隔离,是解决此问题的现代方案。

定义一个泛型的 LocalRepository actor,它内部维护一个内存缓存并负责 JSON 文件的原子读写。所有访问都必须是异步的 (await),从而彻底消除数据竞争。

public actor LocalRepository<T: Codable & Identifiable> where T.ID == String {
    private var cache: [String: T] = [:]
    private let fileURL: URL

    public init(directory: URL = .documentsDirectory, filename: String = "data.json") {
        self.fileURL = directory.appendingPathComponent(filename)
        // 初始化时同步加载
        self.cache = Self.loadSynchronously(from: fileURL)
    }

    public func save(_ item: T) throws {
        cache[item.id] = item
        try persistToFile()
    }

    public func loadAll() -> [T] {
        Array(cache.values)
    }

    private func persistToFile() throws {
        let data = try JSONEncoder().encode(Array(cache.values))
        try data.write(to: fileURL, options: .atomic)
    }
    // ... delete, find 方法类似
}

将这个 Actor 仓库注入到 @Observable 的 ViewModel 中,就能在保证并发安全的前提下,实现数据驱动的 UI 更新。

三、并发模型:Approachable Concurrency 与 @concurrent 调优

Swift 6.2 引入了“Approachable Concurrency”模式,其核心思想是:默认所有异步代码都在调用者的隔离域(通常是主线程)上运行,除非明确指定后台。这大幅降低了意外产生数据竞争的风险。

在 Build Settings 或 Package.swift 中开启相关选项(如 MainActorInference=enabled),编译器会自动推断大部分代码属于 MainActor。例如,一个 @MainActor 类中的 async 函数,默认就在主线程执行。

只有当一个函数执行真正的 CPU 密集型或阻塞 I/O 操作时,才应使用 @concurrent 属性将其标记为后台任务。这通常是性能优化的最后一步。

nonisolated final class ImageProcessor {
    @concurrent
    static func applyFilter(to image: UIImage) async -> UIImage {
        // 模拟耗时的图像处理
        // 这段代码将在后台线程执行
        return processedImage
    }
}

// 调用处
let filteredImage = await ImageProcessor.applyFilter(to: rawImage)

这种模式使得并发控制变得显式且直观。默认安全,按需优化。

四、可测试性:协议依赖注入与 Mock 架构

一个健康的架构必须易于测试。通过协议依赖注入(Protocol-Based DI),我们可以将文件系统、网络、数据库等外部依赖抽象为协议,在生产环境注入真实实现,在测试环境注入可控的 Mock。

为每个外部依赖定义一个单一职责的协议,并确保遵循 Sendable(如果用于并发场景):

protocol UserPreferencesStoring: Sendable {
    func save(_ value: String, forKey key: String) throws
    func load(forKey key: String) throws -> String?
}

然后,为 ViewModelRepository 提供一个接收这些协议的构造函数,并设置默认参数指向真实实现:

@Observable
final class SettingsViewModel {
    private let storage: UserPreferencesStoring

    init(storage: UserPreferencesStoring = RealUserDefaultsStorage()) {
        self.storage = storage
    }

    func saveTheme(_ theme: String) {
        try? storage.save(theme, forKey: "app_theme")
    }
}

在测试中,注入一个 MockUserPreferencesStorage,它可以预设数据、模拟错误,从而验证业务逻辑的所有分支。

final class MockUserPreferencesStorage: UserPreferencesStoring {
    var savedData: [String: String] = [:]
    var errorToThrow: Error?

    func save(_ value: String, forKey key: String) throws {
        if let error = errorToThrow { throw error }
        savedData[key] = value
    }

    func load(forKey key: String) throws -> String? {
        if let error = errorToThrow { throw error }
        return savedData[key]
    }
}

// 测试用例
@Test func testSaveTheme() async {
    let mockStorage = MockUserPreferencesStorage()
    let viewModel = SettingsViewModel(storage: mockStorage)
    viewModel.saveTheme("dark")
    #expect(mockStorage.savedData["app_theme"] == "dark")
}

整合与协作

这四个实践并非孤立存在,它们共同构成一个协同工作的整体:

  1. UI 与数据@Observable 的 ViewModel 持有 LocalRepository Actor 的引用,并通过 await 读取数据,UI 自动响应。
  2. 并发与安全:Swift 6.2 的默认单线程模型保护了大部分 UI 和 ViewModel 逻辑;只有 LocalRepository Actor 内部和标记为 @concurrent 的耗时操作才在后台运行,所有隔离边界都由编译器保证。
  3. 测试与替换LocalRepository 本身也可以被一个遵循相同操作接口的 MockRepository 替换(例如基于内存字典),使得 ViewModel 的测试完全无需真实文件系统,快速且可靠。

通过系统性地应用这些模式,你可以在 Claude Code 等 AI 编程助手的辅助下,高效生成或重构出既享受现代 Swift 语言红利,又具备生产级健壮性和可维护性的应用代码。

FAQ

Q: @Observable 和 ObservableObject 有什么区别? A: @Observable 是 Swift 5.9+ 引入的 Observation 框架宏,它实现属性级的变更追踪,SwiftUI 只会刷新读取了被修改属性的视图,性能更优。而 ObservableObject 配合 @Published 会引发整个对象对应视图的更新。

Q: 什么时候该用 @concurrent,什么时候不该用? A: 只对明确需要后台执行的纯计算或 I/O 阻塞任务使用 @concurrent。对于大多数业务逻辑,尤其是涉及 UI 状态更新的代码,应让其保持在默认的单线程(通常是 MainActor)上运行,以避免不必要的线程切换和数据竞争风险。