Skip to content

Instantly share code, notes, and snippets.

@takehilo
Last active February 5, 2019 02:27
Show Gist options
  • Save takehilo/a4d2785f7c09f192d9bf77ff3cbb9d21 to your computer and use it in GitHub Desktop.
Save takehilo/a4d2785f7c09f192d9bf77ff3cbb9d21 to your computer and use it in GitHub Desktop.
iOS設計パターン入門 第3章 MessageSenderの実装例
import UIKit
import PlaygroundSupport
let page = PlaygroundPage.current
page.needsIndefiniteExecution = true
protocol Message {}
struct TextMessage: Message, Decodable {
var text: String?
}
struct ImageMessage: Message {
var text: String?
let image: UIImage?
}
enum ImageMessageInputError: Error {
case noImage
case tooLongText(count: Int)
}
protocol MessageInput {
associatedtype Payload
func validate() throws -> Payload
}
struct TextMessageInput: MessageInput {
var text: String?
func validate() throws -> TextMessageInput {
if let text = text, text.count >= 80 {
throw ImageMessageInputError.tooLongText(count: text.count) }
return TextMessageInput(text: text)
}
}
struct ImageMessageInput: MessageInput {
var text: String?
var image: UIImage?
func validate() throws -> ImageMessageInput {
guard let image = image else { throw ImageMessageInputError.noImage }
if let text = text, text.count >= 80 {
throw ImageMessageInputError.tooLongText(count: text.count) }
return ImageMessageInput(text: text, image: image)
}
}
protocol MessageSenderAPI {
associatedtype Payload
associatedtype Response: Message
func send(payload: Payload, completion: @escaping (Response?) -> Void)
}
struct TextMessageSenderAPI: MessageSenderAPI {
static let shared = TextMessageSenderAPI()
private init() {}
func send(payload: TextMessageInput, completion: @escaping (TextMessage?) -> Void) {
let url = URL(string: "https://api.example.com/send")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = payload.text?.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
completion(nil)
return
}
do {
let message = try JSONDecoder().decode(TextMessage.self, from: data)
completion(message)
} catch {
completion(nil)
}
}
task.resume()
}
}
protocol MessageSenderDelegate {
func stateChanged<API: MessageSenderAPI, Input: MessageInput>(_ state: MessageSender<API, Input>.State)
}
final class MessageSender<API: MessageSenderAPI, Input: MessageInput> where API.Payload == Input.Payload {
enum State {
case inputting(validationError: Error?)
case sending
case sent(API.Response)
case connectionFailed
init(evaluating input: Input) {
do {
_ = try input.validate()
self = .inputting(validationError: nil)
} catch {
self = .inputting(validationError: error)
}
}
mutating func accept(response: API.Response?) {
if let response = response {
self = .sent(response)
} else {
self = .connectionFailed
}
}
}
private(set) var state: State {
didSet { delegate?.stateChanged(state) }
}
let api: API
var input: Input {
didSet { state = State(evaluating: input) }
}
var delegate: MessageSenderDelegate?
init(api: API, input: Input) {
self.api = api
self.input = input
self.state = State(evaluating: input)
}
func send() {
do {
let payload = try input.validate()
state = .sending
api.send(payload: payload) { [weak self] in
self?.state.accept(response: $0)
}
} catch let e {
state = .inputting(validationError: e)
}
}
}
class Delegate: MessageSenderDelegate {
func stateChanged<API, Input>(_ state: MessageSender<API, Input>.State) {
switch state {
case .sending:
print("Sending...")
case .connectionFailed:
print("Failed to send")
case let .sent(message):
if let textMessage = message as? TextMessage {
print("Successfully sent the message (text: \(textMessage.text ?? ""))")
} else {
print("Successfully sent the message")
}
case let .inputting(error):
if let error = error {
print("Validation error: \(error.localizedDescription)")
}
}
}
}
let api = TextMessageSenderAPI.shared
let input = TextMessageInput(text: "Hello, world!")
let sender = MessageSender(api: api, input: input)
sender.delegate = Delegate()
sender.send()
@takehilo
Copy link
Author

takehilo commented Feb 5, 2019

Swift Playgroundに貼っ付ければそのまま動くはず。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment