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

Generic messages in message-based architecture

我正在 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 来更改它。如果有任何副作用需要计算,它们将被 return 编辑为 SideEffect 消息,并将由其他人处理。每个 SideEffect 消息都有一个“回调”参数,一个 Message 在副作用完成时发送。这很好用。

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

struct Request<ReturnType> { … }

加载请求和 return 类型 ReturnType:

的值有相关的副作用
enum SideEffect<Message> {
    case sendRequest(Request<T>, (T) -> Message)
}

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

无关的副作用

我能否以某种方式创建带有 Request<T>SideEffect 消息,稍后会发送带有 TMessage 消息? (我想我想要 this feature discussed on swift-evolution 之类的东西。)

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

例如,使用模拟 Request<T>(假设它是一个异步操作):

struct Request<T> {

    var mock: T

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

我们可以构建一个 RequestSideEffect<Message> 来保存一个接受给定 (Message) -> Void 回调的闭包,然后对捕获的 Request<T> 实例执行请求,通过 Request<T> 转发结果=19=],然后可以将其结果传递回回调(因此在闭包中保留类型变量 T 'contained'):

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([=11=]))
            }
        }
    }

    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: [=13=]) // no side effects expected
        print(s) // State(something: "foo")
    }
}