SwiftUI - 从架构上讲,逻辑应该放在哪里

SwiftUI - where should logic live architecturally speaking

我是 SwiftUI 的新手,对逻辑应该放在哪里有疑问。显然,由于 SwiftUI 是声明式的并且基于状态,我想避免在 SwiftUI 视图结构中包含大量基于逻辑的代码。但是,在我的示例中,我有一个按钮,当按下该按钮时,应触发以下 POST API 调用:

private func submitIncidentForm(selectedIncident: IncidentReasonResponse) {
    showError = false
    guard let incidentId = selectedIncident.id else { return }
    let args = ["order_id": orderId]
    let body = IncidentRequest(reason: incidentId, employeeId: employeeId, pilotMessage: self.comments)
    
    API.client.post(Endpoint.incident, with: args, using: .put, posting: body, expecting: IncidentResponse.self) { success, response in
        DispatchQueue.asyncMain {
            
            switch success {
            case.success(_):
                if let response = response {
                    self.existingIncidents.append(response)
                    self.selectedIncident = nil
                    self.comments = ""
                }
                
            case .failure(_, _):
                showError = true
            }
        }
    }
}

在理想的 SwiftUI 架构中,它会在何处运行并从何处调用?

第一件事:没有理想的架构。

恕我直言,一个 好的 架构让你有足够的自由以简单的方式表达简单的问题,而不是强迫你写一些胶水 类作用是提供抽象,但随后也增加了复杂性,只是为了分离位于不同文件中的关注点。

尽管如此,在您的情况下,使用 MVVM 或 MVI 模式之一是有意义的。

用户通过点击“提交表单”按钮表达其“意图”,其操作调用函数submit(_ request: IncidentRequest):

struct IncidentRequest { ... }

struct IncidentRequestView: View {
    let viewState: incidentRequestViewState
    let submit: (IncidentRequest) -> Void

    @State var incidentRequest: IncidentRequest = .init()

    var body: some View {
        Button("Submit Form") {
            submit(incidentRequest)
        }
    }
}

闭包submitviewState必须由父视图设置!

请注意,视图本身不处理提交请求。它也不会 也不能 改变它的视图状态,它代表视图呈现自身所需的一切。

视图模型现在最终会收到此“意图”并对其进行处理:

final class IncidentRequestViewModel: ObservableObject {
    @Published private(set) var viewState: IncidentRequestViewState = .init()

    func submit( _ request: IncidentRequest) { 
        ...
    }
}

submit 函数的实现然后会将“提交事件请求”“翻译”为可以发送到模型的合适形式,模型最终负责执行请求。模型更适合发布者,视图模型观察模型。

对于干净的架构,模型将是最终可以执行事件请求的任何具体事物的抽象。也就是说,它不会是一个 API 泄漏实现细节,比如这是一个网络调用,或者你写入 Realm 数据库或 CoreData 等

现在,当您拥有 IncidentRequest 视图、IncidentRequest ViewModel 和 IncidentRequest 模型时,您需要 assemble 这些组件,“某处”。

当您采用像 MVVM 这样的架构时,您将致力于实现这一目标的约定和良好实践。您的团队可能同意使用专用的父视图 (SwiftUI),您可以在其中 assemble 部分:

struct IncidentRequestMainView: View {

    @StateObject var viewModel = 
        IncidentRequestViewModel(model: /* injected */)

    var body: some View {
        IncidentRequestView(
            incidentViewState: viewModel.viewState,
            submit: viewModel.submit)
    }

}

您可能会注意到,纯技术上处理执行此请求所需的所有操作的实际复杂事物(又名 域逻辑)已移至模型。那也是它属于的地方。

此外,纯视觉逻辑似乎在视图模型中(说 什么 呈现 - 又名 Presentation Logic)和查看(说 如何呈现 )。恕我直言,这也是正确的地方。

最后,有一个专门的组件负责 assemble 所有部分。