2017-09-16 35 views
7

Tôi đang thử nghiệm kiến ​​trúc dựa trên thông điệp trong Swift. Tôi đang cố gắng làm một cái gì đó tương tự như kiến ​​trúc Elm, ví dụ. Đây là cách mã của tôi trông:Tin nhắn chung trong kiến ​​trúc dựa trên tin nhắn

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 [] 
     } 
    } 
} 

Vì vậy, nhà nước được mô hình hóa bởi State và bạn có thể thay đổi nó bằng cách gửi Message s. Nếu có bất kỳ tác dụng phụ nào để tính toán, chúng sẽ được trả lại dưới dạng thông báo SideEffect và sẽ được người khác chăm sóc. Mỗi thông báo SideEffect nhận đối số “gọi lại”, Message để gửi khi hiệu ứng phụ hoàn tất. Điều này làm việc tuyệt vời.

Bây giờ, điều gì sẽ xảy ra nếu tôi muốn có thông báo hiệu ứng phụ chung? Tôi muốn có một cái gì đó như thế này:

struct Request<ReturnType> { … } 

Và có một tác dụng phụ liên quan đến tải các yêu cầu và trả về một giá trị kiểu ReturnType:

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

Nhưng điều này (rõ ràng) không biên dịch , vì số case sẽ phải chung chung trên T. Tôi không thể tạo toàn bộ số SideEffect chung trên T, vì có các tác dụng phụ khác không liên quan gì đến T.

Tôi có thể bằng cách nào đó tạo thông báo SideEffect với số Request<T> mà sau này gửi đi Message với T? (Tôi nghĩ rằng tôi muốn một cái gì đó như this feature discussed on swift-evolution.)

+0

Làm thế nào về chúng ta thực hiện một Giao thức 'Returnable' và làm cho 'ReturnType' phù hợp với giao thức này ? Sau đó, chúng ta cũng có thể mở rộng các kiểu khác, như 'String', để phù hợp với giao thức này. – sCha

+0

Bạn sẽ muốn xóa 'T', thường có thể được thực hiện với các đóng (ví dụ bạn đóng một cửa sổ để thực hiện yêu cầu và sau đó chuyển kết quả đến hàm của bạn để tạo ra một thông báo, do đó ẩn kiểu' T' từ thế giới bên ngoài). Tôi không quen thuộc với kiến ​​trúc Elm và do đó không chắc chắn bạn đang mong đợi 'Yêu cầu' được thực hiện như thế nào, nhưng sẽ [một cái gì đó như thế này] (http://swift.sandbox.bluemix.net/#/ repl/59e15aad6cbea87f72c470cc) có khả thi không? – Hamish

+0

Hamish để giải cứu một lần nữa! Tôi nghĩ đó là chính xác những gì tôi cần. Tôi đã đánh bại loại tẩy xoá, nhưng dường như tôi không quen với khái niệm này nên tôi không nghĩ ra giải pháp. Cảm ơn nhiều! Tôi đã gắn cờ câu hỏi để nhận xét của bạn được chuyển đổi thành câu trả lời. – zoul

Trả lời

1

Bạn sẽ muốn xóa xóa T - thường có thể được thực hiện với bao đóng, vì chúng có thể tham chiếu ngữ cảnh từ trang web mà chúng được tạo ra mà không để lộ ngữ cảnh đó với thế giới bên ngoài.

Ví dụ, với một Request<T> (giả sử nó một hoạt động async) giả:

struct Request<T> { 

    var mock: T 

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

Chúng ta có thể xây dựng một RequestSideEffect<Message> chứa một kết thúc mà phải mất một trao (Message) -> Void gọi lại, và sau đó thực hiện một yêu cầu trên bị bắt Request<T> dụ, chuyển tiếp kết quả thông qua một (T) -> Message, kết quả trong số đó có thể sau đó được truyền lại cho gọi lại (vì vậy sẽ giữ kiểu biến T 'có' trong việc đóng cửa):

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) 
    } 
} 

Bây giờ SideEffect<Message> của bạn có thể trông như thế này:

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

Và bạn có thể thực hiện State như thế này:

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") 
    } 
}