You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI应用Clean Swift架构:Interactor向AppState赋值的最佳实践

问题分析与优化方案

你的核心问题是Interactor与AppState的耦合方式,当前单例方案虽然能跑,但存在耦合度高、测试困难、扩展性差的问题,下面给出更符合SwiftUI+Clean Swift规范的优化方案,以及当前代码的修正点。


当前单例方案的问题

  • 强耦合:Interactor和View硬编码依赖AppState.shared,单元测试时无法Mock状态,只能用真实单例,测试不独立
  • 扩展性差:如果以后需要多实例状态(比如多用户场景),单例完全无法支持
  • 不符合Clean Swift的依赖倒置原则:业务逻辑层(Interactor)应该依赖抽象,而非具体的单例实现

更优方案:依赖注入(Dependency Injection)

通过构造器传递AppState和Interactor,彻底解耦各层,同时保留Clean Swift的分层逻辑。

1. 重构AppState(移除单例)

public class AppState: ObservableObject {
    @Published var temperature: Double = 0
}

2. 重构Interactor(构造注入AppState)

public class Interactor {
    private let appState: AppState
    
    // 明确声明依赖,而非偷偷引用单例
    init(appState: AppState) {
        self.appState = appState
    }
    
    // 用Swift并发规范的async/await处理API请求
    func updateData() async {
        // 替换为真实API调用逻辑
        let fetchedTemp = try? await APIService.fetchRealTimeTemperature()
        
        // 必须在主线程更新@Published属性,避免UI异常
        DispatchQueue.main.async {
            self.appState.temperature = fetchedTemp ?? 0
        }
    }
}

3. 重构View(通过环境+构造注入)

用SwiftUI的@EnvironmentObject传递全局状态,避免层层传递;Interactor通过构造器注入,保证视图与业务逻辑解耦:

struct MyView: View {
    @EnvironmentObject private var appState: AppState
    private let interactor: Interactor
    
    init(interactor: Interactor) {
        self.interactor = interactor
    }
    
    var body: some View {
        VStack {
            Text("\(appState.temperature, specifier: "%.1f")°C")
        }
        // 用.task替代onAppear,自动处理任务取消(视图消失时终止请求)
        .task {
            await interactor.updateData()
        }
    }
}

// 在App入口统一注入依赖
@main
struct MyApp: App {
    private let appState = AppState()
    private let interactor: Interactor
    
    init() {
        self.interactor = Interactor(appState: appState)
    }
    
    var body: some Scene {
        WindowGroup {
            MyView(interactor: interactor)
                .environmentObject(appState)
        }
    }
}

进阶:严格遵循Clean Swift分层(适合中型项目)

如果要完全贴合Clean Swift的架构,可以引入Presenter层,让View只和Presenter交互,Interactor只处理业务逻辑:

1. Interactor(只负责数据获取,不碰状态)

public class TemperatureInteractor {
    func fetchTemperature() async throws -> Double {
        return try await APIService.fetchRealTimeTemperature()
    }
}

2. Presenter(处理视图逻辑,更新状态)

public class TemperaturePresenter {
    private let interactor: TemperatureInteractor
    private let appState: AppState
    
    init(interactor: TemperatureInteractor, appState: AppState) {
        self.interactor = interactor
        self.appState = appState
    }
    
    func updateTemperature() async {
        do {
            let temp = try await interactor.fetchTemperature()
            DispatchQueue.main.async {
                self.appState.temperature = temp
            }
        } catch {
            // 统一处理错误
            DispatchQueue.main.async {
                self.appState.temperature = 0
            }
        }
    }
}

3. View(调用Presenter,展示状态)

struct MyView: View {
    @EnvironmentObject private var appState: AppState
    private let presenter: TemperaturePresenter
    
    init(presenter: TemperaturePresenter) {
        self.presenter = presenter
    }
    
    var body: some View {
        VStack {
            Text("\(appState.temperature, specifier: "%.1f")°C")
        }
        .task {
            await presenter.updateTemperature()
        }
    }
}

当前代码的最佳实践修正点

  1. 移除单例:单例仅适合全局唯一资源(如网络配置),业务状态用依赖注入更灵活
  2. 异步规范:用async/await替代闭包,用.task替代onAppear,自动处理任务生命周期
  3. 主线程更新:@Published属性必须在主线程修改,否则会导致UI更新异常
  4. 依赖倒置:可抽象StateStore协议,让AppState遵守,后续替换存储实现无需修改Interactor

内容的提问来源于stack exchange,提问作者erikvee

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 05:10:49