6-12.【架构设计】SwiftUI适合复杂业务架构吗?如何设计一个可测试、可扩展、可替换UI框架的SwiftUI架构?

27 阅读5分钟

一句话结论(先给判断)

SwiftUI 非常适合复杂业务架构,但前提是:你必须把 SwiftUI 限制在“渲染层”,让业务、状态、副作用完全独立于它。

SwiftUI 不应该成为你的架构核心
它应该是一个 可替换的 UI Adapter


一、什么时候 SwiftUI「不适合」复杂业务?

先把坑说清楚:

SwiftUI 不适合复杂业务,通常不是 SwiftUI 的问题,而是使用方式的问题

  • 把业务逻辑写进 View / ViewModel
  • @EnvironmentObject 当全局状态仓库
  • 状态粒度混乱,View 与业务强耦合
  • 副作用分散在 .task / onAppear

👉 结果是:
UI ≈ 业务,换 UI 就等于重写系统


二、复杂业务下的正确分层(核心)

你要的不是“SwiftUI 架构”,而是:

业务架构 + SwiftUI 适配层

推荐的五层结构(实践级)

┌──────────────────────────┐
│        SwiftUI View      │  ← 纯渲染
├──────────────────────────┤
│      UI Adapter Layer    │  ← ViewModel / Presenter
├──────────────────────────┤
│     State / Reducer      │  ← 业务状态机(纯)
├──────────────────────────┤
│   UseCase / Domain       │  ← 业务规则
├──────────────────────────┤
│ Infrastructure / IO      │  ← API / DB / SDK
└──────────────────────────┘

SwiftUI 只占最上面一层


三、一个“可替换 UI”的核心原则

UI 不拥有 State,只观察 State

正确关系:

State → UI
Action ← UI

而不是:

UI → State → UI

四、可测试的关键:纯业务核心

1️⃣ State + Reducer 是纯函数

struct OrderState {
    var items: [Item]
    var total: Double
}

enum OrderAction {
    case add(Item)
    case remove(Item)
}

func orderReducer(
    state: inout OrderState,
    action: OrderAction
) {
    switch action {
    case .add(let item):
        state.items.append(item)
        state.total += item.price
    case .remove(let item):
        ...
    }
}
  • 不依赖 SwiftUI
  • 不依赖线程
  • 不依赖时间

👉 100% 可单元测试


2️⃣ 副作用通过协议注入

protocol OrderService {
    func submit(_ order: OrderState) async throws
}
  • SwiftUI / UIKit 都只是调用方
  • 测试中可 mock

五、SwiftUI 层的正确角色

SwiftUI View 只做三件事

  1. 声明 UI
  2. 绑定 State 的投影
  3. 发 Action
struct OrderView: View {
    let state: OrderState
    let send: (OrderAction) -> Void

    var body: some View {
        List(state.items) { item in
            Text(item.name)
        }
        Button("Submit") {
            send(.submit)
        }
    }
}
  • View 无逻辑
  • 无副作用
  • 无业务判断

六、UI Adapter 层(被严重低估的一层)

你可以叫它:

  • ViewModel
  • Presenter
  • Store
  • Feature Adapter

职责:

  • 把业务 State 映射成 UI 需要的形态
  • 管理 UI 生命周期相关逻辑(但不进业务)
final class OrderViewAdapter: ObservableObject {
    @Published var viewState: OrderViewState

    func send(_ action: OrderAction) {
        ...
    }
}

👉 这一层是 SwiftUI 唯一“认识”的业务层


七、为什么这种架构“可替换 UI”?

因为:

  • UIKit / SwiftUI / Web
    都只需要实现:
render(State)
send(Action)

你的业务核心完全不变。


八、可扩展性的来源

1️⃣ Feature-level 隔离

  • 每个 Feature:

    • State
    • Action
    • Reducer
    • Adapter
  • 无跨 Feature 状态修改

2️⃣ 组合而不是继承

  • State 组合
  • Reducer 组合
  • View 组合

这正是 TCA / Redux 的优势。


九、什么时候用 TCA?什么时候不用?

TCA 非常适合:

  • 多 Feature 协作
  • 强一致性需求
  • 高并发 / 高可测试性

不必 TCA 的情况:

  • 页面级简单逻辑
  • 快速原型

你可以:

核心 Feature 用 TCA,边缘 Feature 用轻量 MVVM


十、真实项目里的“最低风险方案”

“TCA-like,不一定用 TCA”

  • 单一 State
  • Action 驱动
  • Reducer 纯函数
  • SwiftUI 只 render

你已经拿到 80% 的收益。


最终一句话(架构级)

SwiftUI 非常适合复杂业务,但前提是:
它只负责‘画’,而你的系统已经决定了‘发生什么’。

英文版

6-12. [Architecture Design] Is SwiftUI Suitable for Complex Business Logic? How to Design a Testable, Scalable, and UI-Agnostic SwiftUI Architecture?

The Bottom Line (The Verdict)

SwiftUI is highly suitable for complex business architectures, provided that you restrict it to the "Rendering Layer" and keep business logic, state, and side effects completely independent of it.

SwiftUI should not be the core of your architecture; it should be a replaceable UI Adapter.


I. When is SwiftUI "Unsuitable" for Complex Business?

Let's clarify the pitfalls first. When SwiftUI fails in complex scenarios, it’s usually a usage problem, not a framework limitation:

  • Writing business logic directly inside View or ViewModel.
  • Using @EnvironmentObject as a "global dumping ground" for state.
  • Messy state granularity and tight coupling between View and Business.
  • Scattering side effects across .task or .onAppear.

👉 The Result: UI ≈ Business. Changing the UI effectively means rewriting the entire system.


II. Proper Layering for Complex Business (The Core)

What you need isn't a "SwiftUI Architecture," but rather:

A Business Architecture + a SwiftUI Adaptation Layer.

The Recommended 5-Layer Structure (Production-Grade)

LayerComponentResponsibility
1. ViewSwiftUI ViewPure Rendering
2. AdapterViewModel / PresenterUI Logic & State Mapping
3. LogicState / ReducerPure Business State Machine
4. DomainUseCase / EntitiesBusiness Rules & Logic
5. InfraAPI / DB / SDKData Access & Side Effects

SwiftUI only occupies the top-most layer.


III. The Core Principle for "Replaceable UI"

The UI does not own State; it only observes State.

The Correct Relationship:

  • State → UI: The UI is a projection of the state.
  • Action ← UI: The UI sends intents to the system.

Avoid: UI → State → UI (where the UI dictates the state transition logic).


IV. The Key to Testability: A Pure Business Core

1. State + Reducer as Pure Functions

Swift

struct OrderState {
    var items: [Item]
    var total: Double
}

enum OrderAction {
    case add(Item)
    case remove(Item)
}

func orderReducer(state: inout OrderState, action: OrderAction) {
    switch action {
    case .add(let item):
        state.items.append(item)
        state.total += item.price
    case .remove(let item):
        // logic...
    }
}
  • No dependency on SwiftUI.

  • No dependency on threads or time.

    👉 100% Unit Testable.

2. Injecting Side Effects via Protocols

Swift

protocol OrderService {
    func submit(_ order: OrderState) async throws
}
  • SwiftUI and UIKit are merely callers.
  • Easily mockable during testing.

V. The Proper Role of the SwiftUI Layer

A SwiftUI View should only do three things:

  1. Declare UI structure.
  2. Bind to a projection of the State.
  3. Dispatch Actions.

Swift

struct OrderView: View {
    let state: OrderState
    let send: (OrderAction) -> Void

    var body: some View {
        List(state.items) { item in
            Text(item.name)
        }
        Button("Submit") {
            send(.submit)
        }
    }
}
  • Zero logic in the View.
  • Zero side effects.
  • Zero business judgment.

VI. The UI Adapter Layer (The Most Underrated Layer)

You might call this the ViewModel, Presenter, Store, or Feature Adapter.

Responsibilities:

  • Mapping Business State into the specific shape the UI needs.
  • Managing UI-specific lifecycle logic (without leaking into business logic).

👉 This is the only business layer that SwiftUI "knows."


VII. Why Does This Enable "Replaceable UI"?

Because whether you use UIKit, SwiftUI, or even a CLI, they all only need to implement:

  1. render(State)
  2. send(Action)

Your business core remains untouched.


VIII. Sources of Scalability
  1. Feature-Level Isolation: Each feature has its own State, Action, Reducer, and Adapter. No cross-feature state mutation.
  2. Composition over Inheritance: Compose States, Reducers, and Views. This is where TCA (The Composable Architecture) or Redux patterns shine.

IX. When to use TCA?
Use TCA When...Use Lightweight MVVM When...
Multi-feature coordination is required.Page-level simple logic is sufficient.
Strong consistency is a requirement.Building quick prototypes.
High concurrency/testability is needed.The feature is isolated and small.

Pro Tip: You can use TCA for core features and lightweight MVVM for peripheral ones.


X. The "Minimum Risk" Real-World Solution

"TCA-like, without necessarily using the TCA library."

  • Single Source of Truth (Single State).
  • Action-driven flow.
  • Pure function Reducers.
  • SwiftUI for rendering only.

By following these, you reap 80% of the architectural benefits without the library overhead.


Final Architectural Takeaway

SwiftUI is perfectly suited for complex business logic, as long as it is only responsible for "drawing," while your system has already decided "what happens."