基于消息的体系结构中的通用消息



我正在 Swift 中试验基于消息的架构。例如,我正在尝试做一些类似于Elm架构的事情。这是我的代码的样子:

enum SideEffect<Message> {
case sendRequest((String) -> Message)
}
protocol Component {
associatedtype Message
mutating func send(msg: Message) -> [SideEffect<Message>]
}
struct State: Component {
var something: String?
enum Message {
case downloadSomething
case receiveResponse(String)
}
mutating func send(msg: Message) -> [SideEffect<Message>] {
switch msg {
case .downloadSomething:
return [.sendRequest(Message.receiveResponse)]
case .receiveResponse(let response):
something = response
return []
}
}
}

因此,状态由State建模,您可以通过发送Message来更改它。如果有任何副作用需要计算,它们将作为SideEffect消息返回,并由其他人处理。每条SideEffect消息都采用一个"回调"参数,这是副作用完成后发送的Message。这很好用。

现在,如果我想有一个通用的副作用信息怎么办?我想要这样的东西:

struct Request<ReturnType> { … }

并具有相关的副作用来加载请求并返回类型为ReturnType的值:

enum SideEffect<Message> {
case sendRequest(Request<T>, (T) -> Message)
}

但这(显然)无法编译,因为case必须是通用的T。我不能使整个SideEffect通用T,因为还有其他与T无关的副作用。

我可以以某种方式创建一个带有Request<T>SideEffect消息,该消息稍后会T调度Message?(我想我想要在快速进化中讨论类似这个功能的东西。

您需要键入擦除T- 通常这可以通过闭包来完成,因为它们可以从创建它们的站点引用上下文,而不会将该上下文暴露给外部世界。

例如,对于模拟Request<T>(假设它是异步操作):

struct Request<T> {
var mock: T
func doRequest(_ completion: @escaping (T) -> Void) {
// ...
completion(mock)
}
}

我们可以构建一个RequestSideEffect<Message>来保存一个闭包,该闭包接受给定的(Message) -> Void回调,然后在捕获的Request<T>实例上执行请求,通过(T) -> Message转发结果,然后可以将结果传递回回调(从而将类型变量T"包含在闭包中"):

struct RequestSideEffect<Message> {
private let _doRequest: (@escaping (Message) -> Void) -> Void
init<T>(request: Request<T>, nextMessage: @escaping (T) -> Message) {
self._doRequest = { callback in
request.doRequest {
callback(nextMessage($0))
}
}
}
func doRequest(_ completion: @escaping (Message) -> Void) {
_doRequest(completion)
}
}

现在,您的SideEffect<Message>可能如下所示:

enum SideEffect<Message> {
case sendRequest(RequestSideEffect<Message>)
}

你可以像这样实现State

protocol Component {
associatedtype Message
mutating func send(msg: Message) -> [SideEffect<Message>]
}
struct State: Component {
var something: String
enum Message {
case downloadSomething
case receiveResponse(String)
}
mutating func send(msg: Message) -> [SideEffect<Message>] {
switch msg {
case .downloadSomething:
let sideEffect = RequestSideEffect(
request: Request(mock: "foo"), nextMessage: Message.receiveResponse
)
return [.sendRequest(sideEffect)]
case .receiveResponse(let response):
something = response
return []
}
}
}
var s = State(something: "hello")
let sideEffects = s.send(msg: .downloadSomething)
for case .sendRequest(let sideEffect) in sideEffects {
sideEffect.doRequest {
_ = s.send(msg: $0) // no side effects expected
print(s) // State(something: "foo")
}
}

最新更新