Upgrade to Pro — share decks privately, control downloads, hide ads and more …

網路之難,難於上青天 - iPlayground 2019

Wei Wang
September 22, 2019

網路之難,難於上青天 - iPlayground 2019

Wei Wang

September 22, 2019
Tweet

More Decks by Wei Wang

Other Decks in Technology

Transcript

  1. 橕ෝ౯ ሴ ૛ (@onevcat) iOS - 2010 LINE SDK, LINE

    Live Kingfisher, APNGKit, FengNiao etc.
  2. ૡٍ (2010, iOS 4) — ䷱ํ NSURLSession — ䷱ํ Codable

    ҅Ԟ䷱ํ NSJSONSerialization — ( 㫋ݘӧ捧ጱݝํ UITableView)
  3. ض፡፡姜᪠抬࿢Ҙ let request = URLRequest(url: URL(string: "https:!"httpbin.org/get")!) let task =

    URLSession.shared.dataTask(with: request) { (data, response, error) in guard let data = data else { return } let json = try? JSONSerialization.jsonObject(with: data, options: []) guard let dic = json as? [String: Any] else { return } print(dic["url"] as! String) } task.resume()
  4. Ӟ樄ত҅உ墋㻌 let request = URLRequest(url: URL(string: "https:!"httpbin.org/get")!) let task =

    URLSession.shared.dataTask(with: request) { (data, response, error) in guard let data = data else { return } let json = try? JSONSerialization.jsonObject(with: data, options: []) guard let dic = json as? [String: Any] else { return } print(dic["url"] as! String) } task.resume()
  5. Ӟ樄ত҅உ墋㻌 let request = URLRequest(url: URL(string: "https:!"httpbin.org/get")!) let task =

    URLSession.shared.dataTask(with: request) { (data, response, error) in guard let data = data else { return } let json = try? JSONSerialization.jsonObject(with: data, options: []) guard let dic = json as? [String: Any] else { return } print(dic["url"] as! String) } task.resume()
  6. Ӟ樄ত҅உ墋㻌 let request = URLRequest(url: URL(string: "https:!"httpbin.org/get")!) let task =

    URLSession.shared.dataTask(with: request) { (data, response, error) in guard let data = data else { return } let json = try? JSONSerialization.jsonObject(with: data, options: []) guard let dic = json as? [String: Any] else { return } print(dic["url"] as! String) } task.resume()
  7. Ӟ樄ত҅உ墋㻌 let request = URLRequest(url: URL(string: "https:!"httpbin.org/get")!) let task =

    URLSession.shared.dataTask(with: request) { (data, response, error) in guard let data = data else { return } let json = try? JSONSerialization.jsonObject(with: data, options: []) guard let dic = json as? [String: Any] else { return } print(dic["url"] as! String) !" Or whatever you want to do. } task.resume()
  8. Post 抬࿢ !" let request = URLRequest(url: URL(string: "https:!"httpbin.org/get")!) let

    request = URLRequest(url: URL(string: "https:!"httpbin.org/post")!) let task = URLSession.shared.dataTask(with: request) { (data, response, error) in guard let data = data else { return } let json = try? JSONSerialization.jsonObject(with: data, options: []) guard let dic = json as? [String: Any] else { return } !" "bar" print((dic["form"] as! [String: Any])["foo"] as! String) } task.resume()
  9. Post 抬࿢ !" let request = URLRequest(url: URL(string: "https:!"httpbin.org/get")!) var

    request = URLRequest(url: URL(string: "https:!"httpbin.org/post")!) request.httpMethod = "POST" request.httpBody = "foo=bar".data(using: .utf8) let task = URLSession.shared.dataTask(with: request) { (data, response, error) in guard let data = data else { return } let json = try? JSONSerialization.jsonObject(with: data, options: []) guard let dic = json as? [String: Any] else { return } !" "bar" print((dic["form"] as! [String: Any])["foo"] as! String) } task.resume()
  10. Post 抬࿢ !" let request = URLRequest(url: URL(string: "https:!"httpbin.org/get")!) var

    request = URLRequest(url: URL(string: "https:!"httpbin.org/post")!) request.httpMethod = "POST" request.httpBody = "foo=bar".data(using: .utf8) let task = URLSession.shared.dataTask(with: request) { (data, response, error) in guard let data = data else { return } let json = try? JSONSerialization.jsonObject(with: data, options: []) guard let dic = json as? [String: Any] else { return } !" "bar" print((dic["form"] as! [String: Any])["foo"] as! String) } task.resume()
  11. 抬࿢ let url = URL(string: "https:!"httpbin.org/post")! var request = URLRequest(url:

    ) request.httpMethod = "POST" request.httpBody = "foo=bar".data(using: .utf8) struct HTTPRequest { let url: URL let method: String let parameters: [String: Any] func buildRequest() #$ URLRequest { var request = URLRequest(url: url) request.httpMethod = method request.httpBody = parameters .map { "\($0.key)=\($0.value)" } .joined(separator: "&") .data(using: .utf8) return request } }
  12. 抬࿢ !" let url = URL(string: "https:!"httpbin.org/post")! !" var request

    = URLRequest(url: ) !" request.httpMethod = "POST" !" request.httpBody = "foo=bar".data(using: .utf8) struct HTTPRequest { let url: URL let method: String let parameters: [String: Any] func buildRequest() #$ URLRequest { var request = URLRequest(url: url) request.httpMethod = method request.httpBody = parameters .map { "\($0.key)=\($0.value)" } .joined(separator: "&") .data(using: .utf8) return request } }
  13. 抬࿢ !" let url = URL(string: "https:!"httpbin.org/post")! !" var request

    = URLRequest(url: ) !" request.httpMethod = "POST" !" request.httpBody = "foo=bar".data(using: .utf8) struct HTTPRequest { let url: URL let method: String let parameters: [String: Any] func buildRequest() #$ URLRequest { var request = URLRequest(url: url) request.httpMethod = method request.httpBody = parameters .map { "\($0.key)=\($0.value)" } .joined(separator: "&") .data(using: .utf8) return request } }
  14. ࢧ䛑 let decoder = JSONDecoder() struct HTTPResponse<T: Codable> { let

    value: T? let response: HTTPURLResponse? let error: Error? init(data: Data?, response: URLResponse?, error: Error?) throws { self.value = try data.map { try decoder.decode(T.self, from: $0) } self.response = response as? HTTPURLResponse self.error = error } }
  15. Client struct HTTPClient { let session: URLSession func send<T: Codable>(

    _ request: HTTPRequest, handler: @escaping (HTTPResponse<T>?) !" Void) { let urlRequest = request.buildRequest() let task = session.dataTask(with: urlRequest) { data, response, error in handler(try? HTTPResponse( data: data, response: response, error: error)) } task.resume() } }
  16. Client struct HTTPClient { let session: URLSession func send<T: Codable>(

    _ request: HTTPRequest, handler: @escaping (HTTPResponse<T>?) !" Void) { let urlRequest = request.buildRequest() let task = session.dataTask(with: urlRequest) { data, response, error in handler(try? HTTPResponse( data: data, response: response, error: error)) } task.resume() } }
  17. ֵአ Client struct HTTPBinPostResult: Codable { struct Form: Codable {

    let foo: String } let form: Form } let request = HTTPRequest( url: URL(string: "https:!"httpbin.org/post")!, method: "POST", parameters: ["foo": "bar"] ) client.send(request) { (res: HTTPResponse<HTTPBinPostResult>?) in print(res#$value#$form.foo %& "<nil>") !" "bar" }
  18. ֵአ Client struct HTTPBinPostResult: Codable { struct Form: Codable {

    let foo: String } let form: Form } let request = HTTPRequest( url: URL(string: "https:!"httpbin.org/post")!, method: "POST", parameters: ["foo": "bar"] ) client.send(request) { (res: HTTPResponse<HTTPBinPostResult>?) in print(res#$value#$form.foo %& "<nil>") !" "bar" }
  19. ౯㮉؉ԧՋ焒 — HTTPRequest 䌔 url ҅ method ҅ body ੗愇

    — Client ളݑ HTTPRequest ҅旉䟵 URLRequest ҅妔ࢧ Data — HTTPResponse 䌔 (Data?, URLResponse?, Error?) 旉捧凚 T: Codable
  20. දᜉ - Response struct HTTPBinPostResult: Codable { struct Form: Codable

    { let foo: String } let form: Form } let request = HTTPRequest( url: URL(string: "https:!"httpbin.org/post")!, method: "POST", parameters: ["foo": "bar"] ) client.send(request) { (res: HTTPResponse<HTTPBinPostResult>?) in print(res#$value#$form.foo %& "<nil>") !" 㺔氂 1: অग़ ??? !" 㺔氂 2: HTTPBinPostResult 娒捌࢏כ挨Ҙ }
  21. දᜉ - Response !" (data: Data?, response: URLResponse?, error: Error?)

    struct HTTPResponse<T: Codable> { let value: T? let response: HTTPURLResponse? let error: Error? !" #$% } func send<T: Codable>( _ request: HTTPRequest, handler: @escaping (HTTPResponse<T>?) &' Void) { !" #$% }
  22. දᜉ - Response !" (data: Data?, response: URLResponse?, error: Error?)

    struct HTTPResponse<T: Codable> { let value: T? let response: HTTPURLResponse? let error: Error? !" #$% } func send<T: Codable>( _ request: HTTPRequest, handler: @escaping (HTTPResponse<T>?) &' Void) { !" #$% }
  23. දᜉ - Response !" (data: Data?, response: URLResponse?, error: Error?)

    struct HTTPResponse<T: Codable> { let value: T? let response: HTTPURLResponse? let error: Error? !" #$% } func send<T: Codable>( _ request: HTTPRequest, handler: @escaping (Result<T, Error>) &' Void) { !" #$% }
  24. දᜉ - Response func send<T: Codable>( _ request: HTTPRequest, handler:

    @escaping (Result<T, Error>) !" Void) { let urlRequest = request.buildRequest() let task = session.dataTask(with: urlRequest) { data, response, error in guard let data = data else { handler(.failure(error #$ ResponseError.nilData)) return } do { let value = try decoder.decode(T.self, from: data) handler(.success(value)) } catch { handler(.failure(error)) } } task.resume() }
  25. දᜉ - Response func send<T: Codable>( _ request: HTTPRequest, handler:

    @escaping (Result<T, Error>) !" Void) { let urlRequest = request.buildRequest() let task = session.dataTask(with: urlRequest) { data, response, error in guard let data = data else { handler(.failure(error #$ ResponseError.nilData)) return } do { let value = try decoder.decode(T.self, from: data) handler(.success(value)) } catch { handler(.failure(error)) } } task.resume() }
  26. දᜉ - Response struct HTTPBinPostResult: Codable { struct Form: Codable

    { let foo: String } let form: Form } let request = HTTPRequest( url: URL(string: "https:!"httpbin.org/post")!, method: "POST", parameters: ["foo": "bar"] ) client.send(request) { (res: HTTPResponse<HTTPBinPostResult>?) in print(res#$value#$form.foo %& "<nil>") !" "bar" }
  27. දᜉ - Response struct HTTPBinPostResult: Codable { struct Form: Codable

    { let foo: String } let form: Form } let request = HTTPRequest( url: URL(string: "https:!"httpbin.org/post")!, method: "POST", parameters: ["foo": "bar"] ) client.send(request) { (res: Result<HTTPBinPostResult, Error>) in switch res { case .success(let value): print(value.form.foo) case .failure(let error): print(error) } }
  28. දᜉ - Request struct HTTPBinPostResult: Codable { struct Form: Codable

    { let foo: String } let form: Form } let request = HTTPRequest( url: URL(string: "https:!"httpbin.org/post")!, method: "POST", parameters: ["foo": "bar"] ) client.send(request) { (res: Result<HTTPBinPostResult, Error>) in switch res { case .success(let value): print(value.form.foo) case .failure(let error): print(error) } }
  29. දᜉ - Request protocol Request { associatedtype Response: Decodable var

    url: URL { get } var method: String { get } var parameters: [String: Any] { get } } extension Request { func buildRequest() !" URLRequest }
  30. දᜉ - Request struct HTTPBinPostRequest: Request { typealias Response =

    HTTPBinPostResult let url = URL(string: "https:!"httpbin.org/post")! let method = HTTPMethod.POST let foo: String var parameters: [String : Any] { return ["foo": foo] } }
  31. දᜉ - Request let request = HTTPRequest( url: URL(string: "https:!"httpbin.org/post")!,

    method: "POST", parameters: ["foo": "bar"] ) client.send(request) { (res: Result<HTTPBinPostResult, Error>) in switch res { case .success(let value): print(value.form.foo) case .failure(let error): print(error) } }
  32. දᜉ - Request !" let request = HTTPRequest( !" url:

    URL(string: "https:!"httpbin.org/post")!, !" method: "POST", !" parameters: ["foo": "bar"] !" ) let request = HTTPBinPostRequest(foo: "bar") client.send(request) { (res: Result<HTTPBinPostResult, Error>) in switch res { case .success(let value): print(value.form.foo) case .failure(let error): print(error) } }
  33. ౯㮉؉ԧՋ焒 — Request protocol ು᨝ — Response Type አ associatedtype

    ޾ Request 昧ള᩸㬵 — አ Result<T, Error> 墋۸ Response
  34. ౯㮉஑کՋ焒 !" ਠᗦጱ抬࿢ොୗ let request = HTTPBinPostRequest(foo: "bar") client.send(request) {

    res in switch res { case .success(let value): print(value.form.foo) case .failure(let error): print(error) } }
  35. Case 1 - Request request.httpBody = parameters .map { "\($0.key)=\($0.value)"

    } .joined(separator: "&") !" foo=bar&flag=1 .data(using: .utf8)
  36. Case 1 - Request request.httpBody = parameters .map { "\($0.key)=\($0.value)"

    } .joined(separator: "&") !" foo=bar&flag=1 .data(using: .utf8) !" JSON Body? {"foo": "bar", "flag" = 1} !" Query in URL? not HTTP body url = "https:!"example.com?foo=bar&flag=1"
  37. Case 1 - Request func buildRequest() !" URLRequest { #$%&'

    if method () "GET" { var components = URLComponents( url: url, resolvingAgainstBaseURL: false)! components.queryItems = parameters.map { URLQueryItem(name: $0.key, value: $0.value as? String) } request.url = components.url } else { if headers["Content-Type"] () "application/json" { request.httpBody = try? JSONSerialization .data(withJSONObject: parameters, options: []) } else if headers["Content-Type"] () "application/x-*+,-form-urlencoded" { #$ TODO: Read RFC 3986. How to encode something like array? request.httpBody = parameters .map { "\($0.key)=\($0.value)" } .joined(separator: "&") .data(using: .utf8) } else { #$%&' } } return request }
  38. Case 1 - Request func buildRequest() !" URLRequest { #$%&'

    if method () "GET" { var components = URLComponents( url: url, resolvingAgainstBaseURL: false)! components.queryItems = parameters.map { URLQueryItem(name: $0.key, value: $0.value as? String) } request.url = components.url } else *+ POST, PUT, DELET, etc. ,- { if headers["Content-Type"] () "application/json" { request.httpBody = try? JSONSerialization .data(withJSONObject: parameters, options: []) } else if headers["Content-Type"] () "application/x-./0-form-urlencoded" { #$ TODO: Read RFC 3986. How to encode something like array? request.httpBody = parameters .map { "\($0.key)=\($0.value)" } .joined(separator: "&") .data(using: .utf8) } else *+ multipart/form-data, text/xml, etc ,- { #$%&' } } return request }
  39. Case 1 - Request func buildRequest() !" URLRequest { #$%&'

    if method () "GET" { var components = URLComponents( url: url, resolvingAgainstBaseURL: false)! components.queryItems = parameters.map { URLQueryItem(name: $0.key, value: $0.value as? String) } request.url = components.url } else *+ POST, PUT, DELET, etc. ,- { if headers["Content-Type"] () "application/json" { request.httpBody = try? JSONSerialization .data(withJSONObject: parameters, options: []) } else if headers["Content-Type"] () "application/x-./0-form-urlencoded" { #$ TODO: Read RFC 3986. How to encode something like array? request.httpBody = parameters .map { "\($0.key)=\($0.value)" } .joined(separator: "&") .data(using: .utf8) } else *+ multipart/form-data, text/xml, etc ,- { #$%&' } } return request }
  40. Case 2 - Response struct HTTPBinPostResult: Codable { struct Form:

    Codable { let foo: String } let form: Form }
  41. Case 2 - Response struct HTTPBinPostResult: Codable { struct Form:

    Codable { let foo: String } let form: Form } !" 200 OK { "form": { "foo": "bar" } }
  42. Case 2 - Response struct HTTPBinPostResult: Codable { struct Form:

    Codable { let foo: String } let form: Form } !" 403 Forbidden { "error": true, "code": 403, "reason": "Invalid Token." }
  43. Case 2 - Response session.dataTask(with: urlRequest) { data, response, error

    in guard let data = data else { handler(.failure(error !" ResponseError.nilData)) return } do { let value = try decoder.decode(Req.Response.self, from: data) handler(.success(value)) } catch { handler(.failure(error)) } }
  44. Case 2 - Response session.dataTask(with: urlRequest) { data, response, error

    in guard let data = data else { handler(.failure(error !" ResponseError.nilData)) return } guard let response = response as? HTTPURLResponse else { handler(.failure(ResponseError.nonHTTPResponse)) return } if response.statusCode #$ 300 { do { let error = try decoder.decode(APIError.self, from: data) handler(.failure( ResponseError.apiError( error: error, statusCode: response.statusCode) ) ) } catch { handler(.failure(error)) } } do { let value = try decoder.decode(T.self, from: data) handler(.success(value)) } catch { handler(.failure(error)) } }
  45. Case 3 - Client: ᯿手 func send<T: Codable>( _ request:

    HTTPRequest, leftRetryCount: Int = 2, handler: @escaping (Result<T, Error>) !" Void)
  46. Case 3 - Client: ᯿手 session.dataTask(with: urlRequest) { data, response,

    error in if let error = error { handler(.failure(error)) return } guard let data = data else { handler(.failure(ResponseError.nilData)) return } guard let response = response as? HTTPURLResponse else { handler(.failure(ResponseError.nonHTTPResponse)) return } if response.statusCode !" 300 { if leftRetryCount > 0 { self.send(request, leftRetryCount: leftRetryCount - 1, handler: handler) } else { do { let error = try decoder.decode(APIError.self, from: data) handler(.failure( ResponseError.apiError( error: error, statusCode: response.statusCode) ) ) } catch { handler(.failure(error)) } } } do { let value = try decoder.decode(T.self, from: data) handler(.success(value)) } catch { handler(.failure(error)) } }
  47. Case 3 - Client: ๅෛ token !" ইຎ制䙪嘨ฎ 403 Ӭ

    error code ฎ 999, !" ڬෛ token҅ԏ஍᯿ෛ抬࿢ܻ API struct RefreshTokenRequest: Request { #$ %& }
  48. Case 3 - Client: ๅෛ token session.dataTask(with: urlRequest) { data,

    response, error in if let error = error { handler(.failure(error)) return } guard let data = data else { handler(.failure(ResponseError.nilData)) return } guard let response = response as? HTTPURLResponse else { handler(.failure(ResponseError.nonHTTPResponse)) return } if response.statusCode !" 300 { do { let error = try decoder.decode(APIError.self, from: data) if response.statusCode #$ 403 %& error.code #$ 999 { let freshTokenRequest = RefreshTokenRequest(refreshToken: "token123") self.send(freshTokenRequest) { result in switch result { case .success(let token): keyChain.saveToken(result) '( Send current request again. self.send(request, handler: handler) case .failure(let error): handler(.failure(ResponseError.tokenError)) } } return } else { handler(.failure( ResponseError.apiError( error: error, statusCode: response.statusCode) ) ) } } catch { handler(.failure(error)) } } do { let value = try decoder.decode(T.self, from: data) handler(.success(value)) } catch { handler(.failure(error)) } }
  49. Case 3 - Client: 揾ා旉䟵 session.dataTask(with: urlRequest) { data, response,

    error in if let error = error { handler(.failure(error)) return } guard let data = data else { handler(.failure(ResponseError.nilData)) return } guard let response = response as? HTTPURLResponse else { handler(.failure(ResponseError.nonHTTPResponse)) return } if response.statusCode !" 300 { do { let error = try decoder.decode(APIError.self, from: data) if response.statusCode #$ 403 %& error.code #$ 999 { let freshTokenRequest = RefreshTokenRequest(refreshToken: "token123") self.send(freshTokenRequest) { result in switch result { case .success(let token): keyChain.saveToken(result) '( Send current request again. self.send(request, handler: handler) case .failure(let error): handler(.failure(ResponseError.tokenError)) } } return } else { handler(.failure( ResponseError.apiError( error: error, statusCode: response.statusCode) ) ) } } catch { handler(.failure(error)) } } do { let realData = data.isEmpty ? "{}".data(using: .utf8)! : data let value = try decoder.decode(T.self, from: realData) handler(.success(value)) } catch { handler(.failure(error)) } }
  50. ᗦঅጱቘమӮኴ struct HTTPClient { func send<T: Codable>( _ request: HTTPRequest,

    handler: @escaping (Result<T, Error>) !" Void) { let urlRequest = request.buildRequest() let task = session.dataTask(with: urlRequest) { data, response, error in guard let data = data else { handler(.failure(error #$ ResponseError.nilData)) return } do { let value = try decoder.decode(T.self, from: data) handler(.success(value)) } catch { handler(.failure(error)) } } task.resume() } }
  51. 䵝ᯡጱ匍䋿Ӯኴ protocol Request { associatedtype Response: Decodable var url: URL

    { get } var method: HTTPMethod { get } var parameters: [String: Any] { get } var contentType: ContentType { get } } extension Request { func buildRequest() !" URLRequest { var request = URLRequest(url: url) request.httpMethod = method.rawValue if method #$ .GET { var components = URLComponents( url: url, resolvingAgainstBaseURL: false)! components.queryItems = parameters.map { URLQueryItem(name: $0.key, value: $0.value as? String) } request.url = components.url } else { if contentType.rawValue.contains("application/json") { request.httpBody = try? JSONSerialization .data(withJSONObject: parameters, options: []) } else if contentType.rawValue.contains("application/x-%&'-form-urlencoded") { request.httpBody = parameters .map { "\($0.key)=\($0.value)" } .joined(separator: "&") .data(using: .utf8) } else { ()*+, } } return request } } struct APIError: Decodable { let code: Int let reason: String } struct RefreshTokenRequest: Request { struct Response: Decodable { let token: String } let url = URL(string: "someurl")! let method: HTTPMethod = .POST let contentType: ContentType = .json var parameters: [String : Any] { return ["refreshToken": refreshToken] } let refreshToken: String } class IndicatorManager { static var currentCount = 0 static func increase() { currentCount += 1 if currentCount -. 0 { UIApplication.shared .isNetworkActivityIndicatorVisible = true } } static func decrease() { currentCount = max(0, currentCount - 1) if currentCount #$ 0 { UIApplication.shared .isNetworkActivityIndicatorVisible = false } } } struct HTTPClient { let session: URLSession init(session: URLSession) { self.session = session } func send<T: Codable>( _ request: HTTPRequest, handler: @escaping (Result<T, Error>) !" Void) { let urlRequest = request.buildRequest() IndicatorManager.increase() let task = session.dataTask(with: urlRequest) { data, response, error in DispatchQueue.main.async { IndicatorManager.decrease() } if let error = error { handler(.failure(error)) return } guard let data = data else { handler(.failure(ResponseError.nilData)) return } guard let response = response as? HTTPURLResponse else { handler(.failure(ResponseError.nonHTTPResponse)) return } if response.statusCode -. 300 { do { let error = try decoder.decode(APIError.self, from: data) if response.statusCode #$ 403 /0 error.code #$ 999 { let freshTokenRequest = RefreshTokenRequest(refreshToken: "token123") self.send(freshTokenRequest) { result in switch result { case .success(let token): () keyChain.saveToken(result) () Send current request again. self.send(request, handler: handler) case .failure: handler(.failure(ResponseError.tokenError)) } } return } else { handler(.failure( ResponseError.apiError( error: error, statusCode: response.statusCode) ) ) } } catch { handler(.failure(error)) } } do { let value = try decoder.decode(T.self, from: data) handler(.success(value)) } catch { handler(.failure(error)) } } task.resume() } func send<Req: Request>( _ request: Req, handler: @escaping (Result<Req.Response, Error>) !" Void) { let urlRequest = request.buildRequest() let task = session.dataTask(with: urlRequest) { data, response, error in guard let data = data else { handler(.failure(error 12 ResponseError.nilData)) return } do { let value = try decoder.decode(Req.Response.self, from: data) handler(.success(value)) } catch { handler(.failure(error)) } } task.resume() } }
  52. ᯈᗝ抬࿢ — GET, POST, !"# — httpMethod — JSON, Form,

    !"# — httpBody — Token, User agent — setValue(_:forHTTPHeaderField:)
  53. HTTP Method Adapter struct AnyAdapter: RequestAdapter { let block: (URLRequest)

    throws !" URLRequest func adapted(_ request: URLRequest) throws !" URLRequest { return try block(request) } } enum HTTPMethod: String { case GET, POST var adapter: AnyAdapter { return AnyAdapter { req in var req = req req.httpMethod = self.rawValue return req } } }
  54. HTTP Method Adapter struct AnyAdapter: RequestAdapter { let block: (URLRequest)

    throws !" URLRequest func adapted(_ request: URLRequest) throws !" URLRequest { return try block(request) } } enum HTTPMethod: String { case GET, POST var adapter: AnyAdapter { return AnyAdapter { req in var req = req req.httpMethod = self.rawValue return req } } }
  55. HTTP Method Adapter extension Request { func buildRequest() !" URLRequest

    { var request = URLRequest(url: url) request.httpMethod = method.rawValue #$%&' } }
  56. HTTP Method Adapter extension Request { func buildRequest() throws !"

    URLRequest { var request = URLRequest(url: url) #$ request.httpMethod = method.rawValue request = try method.adapter.adapted(request) #$%&' } }
  57. Content Adapter enum ContentType: String { case json = "application/json"

    case urlForm = "application/x-!"#-form-urlencoded" var headerAdapter: AnyAdapter { return AnyAdapter { req in $% &'( } } }
  58. Content Adapter enum ContentType: String { case json = "application/json"

    case urlForm = "application/x-!"#-form-urlencoded" $% &'( func dataAdapter( for parameters: [String: Any] ) )* RequestAdapter { switch self { case .json: return JSONRequestDataAdapter(parameters: parameters) case .urlForm: return URLFormRequestDataAdapter(parameters: parameters) } } }
  59. Content Adapter extension Request { func buildRequest() throws !" URLRequest

    { #$ %&' if method () .GET { var components = URLComponents( url: url, resolvingAgainstBaseURL: false)! components.queryItems = parameters.map { URLQueryItem(name: $0.key, value: $0.value as? String) } request.url = components.url } else { if contentType.rawValue.contains("application/json") { request.httpBody = try? JSONSerialization .data(withJSONObject: parameters, options: []) } else if contentType.rawValue.contains("application/x-*+,-form-urlencoded") { request.httpBody = parameters .map { "\($0.key)=\($0.value)" } .joined(separator: "&") .data(using: .utf8) } else { #$%&' } } } }
  60. Content Adapter extension Request { func buildRequest() throws !" URLRequest

    { #$ %&' request = try RequestContentAdapter( method: method, contentType: contentType, content: parameters) .adapted(request) } }
  61. ग़㮆 Adapter extension Request { func buildRequest() throws !" URLRequest

    { var request = URLRequest(url: url) request = try method.adapter .adapted(request) request = try RequestContentAdapter( method: method, contentType: contentType, content: parameters) .adapted(request) #$ More adapters%&' return request } }
  62. Request protocol Ӿጱ Adapter protocol Request { associatedtype Response: Decodable

    var url: URL { get } var method: HTTPMethod { get } var parameters: [String: Any] { get } var contentType: ContentType { get } var adapters: [RequestAdapter] { get } }
  63. Request protocol Ӿጱ Adapter extension Request { var adapters: [RequestAdapter]

    { return [ method.adapter, RequestContentAdapter( method: method, contentType: contentType, content: parameters) !" More adapters#$% ] } func buildRequest() throws &' URLRequest { let request = URLRequest(url: url) return try adapters.reduce(request) { try $1.adapted($0) } } }
  64. Request protocol Ӿጱ Adapter extension Request { var adapters: [RequestAdapter]

    { return [ method.adapter, RequestContentAdapter( method: method, contentType: contentType, content: parameters) !" More adapters#$% ] } func buildRequest() throws &' URLRequest { let request = URLRequest(url: url) return try adapters.reduce(request) { try $1.adapted($0) } } }
  65. ౯㮉؉ԧՋ焒 — 㴕ୌ RequestAdapter protocol ( ਧ嬝᭗አጱ 䯤ୌ URLRequest ጱොୗ

    ) — ֵ HTTPMethod ޾ ContentType ඪൔ RequestAdapter — 凚 Request ᯈᗝ [RequestAdapter]
  66. Decision Action enum DecisionAction<Req: Request> { case continueWith(Data, HTTPURLResponse) case

    restartWith([Decision]) case errored(Error) case done(Req.Response) }
  67. Decision Protocol protocol Decision { func shouldApply<Req: Request>( request: Req,

    data: Data, response: HTTPURLResponse) !" Bool func apply<Req: Request>( request: Req, data: Data, response: HTTPURLResponse, done closure: @escaping (DecisionAction<Req>) !" Void) }
  68. Parsing Decision struct ParseResultDecision: Decision { func shouldApply<Req: Request>( request:

    Req, data: Data, response: HTTPURLResponse) !" Bool { return true } func apply<Req: Request>( request: Req, data: Data, response: HTTPURLResponse, done closure: @escaping (DecisionAction<Req>) !" Void) { do { let value = try decoder.decode(Req.Response.self, from: data) closure(.done(value)) } catch { closure(.errored(error)) } } }
  69. Parsing Decision struct ParseResultDecision: Decision { func shouldApply<Req: Request>( request:

    Req, data: Data, response: HTTPURLResponse) !" Bool { return true } func apply<Req: Request>( request: Req, data: Data, response: HTTPURLResponse, done closure: @escaping (DecisionAction<Req>) !" Void) { do { let value = try decoder.decode(Req.Response.self, from: data) closure(.done(value)) } catch { closure(.errored(error)) } } }
  70. ๅग़ໜֺ struct BadResponseStatusCodeDecision: Decision { func shouldApply !" #$ {

    return !(200%&'300).contains(response.statusCode) } func apply !" #$ { do { let value = try decoder.decode(APIError.self, from: data) let apiError = ResponseError.apiError( error: value, statusCode: response.statusCode) closure(.errored(apiError)) } catch { closure(.errored(error)) } } }
  71. ๅग़ໜֺ struct BadResponseStatusCodeDecision: Decision { func shouldApply !" #$ {

    return !(200%&'300).contains(response.statusCode) } func apply !" #$ { do { let value = try decoder.decode(APIError.self, from: data) let error = ResponseError.apiError( error: value, statusCode: response.statusCode) closure(.errored(error)) } catch { closure(.errored(error)) } } }
  72. ๅग़ໜֺ struct DataMappingDecision: Decision { let condition: (Data) !" Bool

    let transform: (Data) !" Data func shouldApply #$ %& { return condition(data) } func apply #$ %& { closure(.continueWith(transform(data), response)) } }
  73. Request protocol Ӿጱ Decision protocol Request { associatedtype Response: Decodable

    var url: URL { get } var method: HTTPMethod { get } var parameters: [String: Any] { get } var contentType: ContentType { get } var adapters: [RequestAdapter] { get } var decisions: [Decision] { get } }
  74. Request protocol Ӿጱ Decision extension Request { !" #$% var

    decisions: [Decision] { return [ RefreshTokenDecision(), RetryDecision(leftCount: 2), BadResponseStatusCodeDecision(), DataMappingDecision( condition: { $0.isEmpty }, transform: { _ in "{}".data(using: .utf8)! } ), ParseResultDecision() ] } }
  75. 归ቘ Decision Actions if currentDecision.shouldApply(!" #$) { currentDecision.apply(!" #$) {

    action in switch action { case .continueWith(let data, let response): self.handleDecision(!" #$) case .restartWith(let decisions): self.send(!" #$) case .errored(let error): handler(.failure(error)) case .done(let value): handler(.success(value)) } } } else { handleDecision(!" #$) }
  76. 归ቘ Decision Actions if currentDecision.shouldApply(!" #$) { currentDecision.apply(!" #$) {

    action in switch action { case .continueWith(let data, let response): self.handleDecision(!" #$) %& Next decision case .restartWith(let decisions): self.send(!" #$) %& Restart request case .errored(let error): handler(.failure(error)) %& Finish with error case .done(let value): handler(.success(value)) %& Finish with value } } } else { handleDecision(!" #$) }
  77. ๋஍... struct HTTPBinPostRequest: Request { struct Response: Codable { struct

    Form: Codable { let foo: String } let form: Form } let url = URL(string: "https:!"httpbin.org/post")! let method = HTTPMethod.POST let contentType = ContentType.urlForm var parameters: [String : Any] { return ["foo": foo] } let foo: String }
  78. ๋஍... !" क़ࣁ䷱ํ捧҅㲌ࣁय़ӧݶ let request = HTTPBinPostRequest(foo: "bar") client.send(request) {

    res in switch res { case .success(let value): print(value.form.foo) case .failure(let error): print(error) } }
  79. ᭐晃 protocol ཛྷ奲۸晁ᤈ extension Request { var adapters: [RequestAdapter] {

    return [ method.adapter, RequestContentAdapter( method: method, contentType: contentType, content: parameters) ] } var decisions: [Decision] { return [ RefreshTokenDecision(), RetryDecision(leftCount: 2), BadResponseStatusCodeDecision(), DataMappingDecision(condition: { $0.isEmpty }) { _ in return "{}".data(using: .utf8)! }, ParseResultDecision() ] } }