2017-09-16 65 views
7

我正在Swift中試驗基於消息的體系結構。例如,我正在嘗試類似於Elm Architecture。這是我的代碼的外觀:基於消息的體系結構中的通用消息

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沒有任何關係。

我可以以某種方式創建一個SideEffect消息與Request<T>,稍後將派遣MessageT? (我想我想要類似this feature discussed on swift-evolution。)

+0

怎麼樣,我們做一個協議'Returnable',使'ReturnType'符合本協議?然後我們也可以擴展其他類型,比如'String',以符合這個協議。 – sCha

+0

您需要鍵入擦除'T',通常這可以通過關閉來完成(例如,您執行一個閉包來執行請求,然後將結果傳遞給您的函數,然後產生一條消息,從而隱藏' T'來自外部世界)。我完全不熟悉Elm架構,因此我不確定你如何期待'Request'被實現,但會[像這樣的東西](http://swift.sandbox.bluemix.net/#/ repl/59e15aad6cbea87f72c470cc)是否可行? – Hamish

+0

再次趕緊救援!我認爲這正是我需要的。我在類型擦除時捱打,但顯然我不太習慣這個概念,所以我沒有想出解決方案。非常感謝你!我已將此問題標記爲將您的評論轉換爲答案。 – zoul

回答

1

您需要鍵入擦除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") 
    } 
}