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() } } }
当前代码的最佳实践修正点
- 移除单例:单例仅适合全局唯一资源(如网络配置),业务状态用依赖注入更灵活
- 异步规范:用
async/await替代闭包,用.task替代onAppear,自动处理任务生命周期 - 主线程更新:
@Published属性必须在主线程修改,否则会导致UI更新异常 - 依赖倒置:可抽象
StateStore协议,让AppState遵守,后续替换存储实现无需修改Interactor
内容的提问来源于stack exchange,提问作者erikvee
相关产品推荐
相关产品推荐

