Skip to content

Instantly share code, notes, and snippets.

@takasek
Created February 11, 2019 10:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save takasek/a8d7cffe7856a14973f28c112bc89c47 to your computer and use it in GitHub Desktop.
Save takasek/a8d7cffe7856a14973f28c112bc89c47 to your computer and use it in GitHub Desktop.
iOSアプリ設計パターン入門 第3章サンプルコード
protocol MessageSenderDelegate {
func stateの変化を伝える()
}
protocol Message {
}
protocol MessageInput {
associatedtype Payload
func validate() throws -> Payload
}
protocol MessageSenderAPI {
associatedtype Payload
associatedtype Response: Message
func send(payload: Payload, completion: @escaping (Response?) -> Void)
}
final class MessageSender<API: MessageSenderAPI, Input: MessageInput>
where API.Payload == Input.Payload {
var delegate: MessageSenderDelegate?
let api: API
var input: Input {
didSet { state = State(evaluating: input) }
}
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 let e {
self = .inputting(validationError: e)
}
}
mutating func accept(response: API.Response?) {
self = response.map(State.sent) ?? .connectionFailed
}
}
private(set) var state: State {
didSet { delegate?.stateの変化を伝える() }
}
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)
}
}
}
enum ImageMessageInputError: Error {
case noImage, tooLongText(count: Int)
}
struct ImageMessageInput: MessageInput {
var text: String?
var image: UIImage?
func validate() throws -> (text: String?, image: UIImage) {
guard let image = image
else { throw ImageMessageInputError.noImage }
if let text = text, text.count >= 80
{ throw ImageMessageInputError.tooLongText(count: text.count) }
return (text, image)
}
}
struct ImageMessage: Message {
let id: Int
let text: String?
let imageURL: URL
}
final class ImageMessageSenderAPI: MessageSenderAPI {
var successes: Bool = true
func send(payload: (text: String?, image: UIImage), completion: @escaping (ImageMessage?) -> Void) {
if successes {
completion(ImageMessage(id: 1, text: payload.text, imageURL: URL(string: "localhost://")!))
} else {
completion(nil)
}
}
}
let api = ImageMessageSenderAPI()
let sender = MessageSender(
api: api,
input: ImageMessageInput(text: nil, image: nil)
)
class SpyMessageSenderDelegate: MessageSenderDelegate {
func stateの変化を伝える() {
print(sender.state)
}
}
sender.delegate = SpyMessageSenderDelegate()
sender.input.text = String(repeating: "", count: 100)
sender.input.image = nil
sender.send()
sender.input.image = UIImage()
sender.send()
sender.input.text = "ほげほげ"
api.successes = false
sender.send()
sender.send()
api.successes = true
sender.send()
/*
inputting(validationError: Optional(__lldb_expr_15.ImageMessageInputError.noImage))
inputting(validationError: Optional(__lldb_expr_15.ImageMessageInputError.noImage))
inputting(validationError: Optional(__lldb_expr_15.ImageMessageInputError.noImage))
inputting(validationError: Optional(__lldb_expr_15.ImageMessageInputError.tooLongText(count: 100)))
inputting(validationError: Optional(__lldb_expr_15.ImageMessageInputError.tooLongText(count: 100)))
inputting(validationError: nil)
sending
connectionFailed
sending
connectionFailed
sending
sent(__lldb_expr_15.ImageMessage(id: 1, text: Optional("ほげほげ"), imageURL: localhost://))
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment