Slide 1

Slide 1 text

.PEFSOBOE1SBDUJDBM /FUXPSLJOHJO4XJGU 5BLFUP:PTIJEB 姏莸⠁䏟UFDI

Slide 2

Slide 2 text

)FMMP 5BLFUP:PTIJEB!UBNBOZBO J04%FWFMPQFS!/JLLFJ 秵꬗ؽُ٦،٦،فٔךꟚ涪罏 4XJGUָ㥨ֹדׅ%

Slide 3

Slide 3 text

طحزٙ٦ؙفؚٗٓىؚٝכꅾ銲

Slide 4

Slide 4 text

"1*$MJFOUJTOPU&BTZ ؔؿٓ؎ٝ儗ך׋׭חر٦ة׾ٍؗحءُׅ׷ "1*ؙٔؒأزָ㣟侁׃׋㜥さٔزٓ؎׾遤ֲ 㹋⹡ח黝׃׋"1*ؙٓ؎،ٝز׾⡲䧭׃׋ְ

Slide 5

Slide 5 text

㹋⹡ِ٦أ؛٦أחさ׻ׇ׋ "1*$MJFOU׾⡲䧭ׅ׷חכ

Slide 6

Slide 6 text

IUUQTHJUIVCDPNUBNBOZBO,BCV[J;B5FDI ؟ٝفٕ؝٦س

Slide 7

Slide 7 text

1SPUPDPM0SJFOUFE /FUXPSL1SPHSBNNJOH

Slide 8

Slide 8 text

4XJGUד"1*$MJFOU׾⡲׷ 6TF"MBNPSF

Slide 9

Slide 9 text

protocol RequestType { associatedtype ResponseType var baseURL: String { get } var method: HTTPMethod { get } var path: String { get } var parameters: [String: AnyObject] { get } var headers: [String: String] { get } /* HTTPのResponse Bodyからオブジェクトを返す */ func responseFromObject(_ object: Data) -> ResponseType? } extension RequestType { var baseURL: String { return "https://..." } var headers: [String: String] { return [:] } var parameters: [String: AnyObject] { return [:] } var URL: String { return self.baseURL + self.path } } ,BCV[J;B5FDITXJGU

Slide 10

Slide 10 text

class APIClient { func sendRequest(_ request: T, completion: @escaping (Result) -> Void) { Alamofire.request(request.URL, method: request.method, parameters: request.parameters, headers: request.headers).responseData { response in switch response.result { case .success(let value): guard let responseData = request.responseFromObject(value) else { return completion(Result.failure(/* fail to parse error */)) } return completion(Result.success(responseData)) case .failure(let error): return completion(Result.failure(error)) } } } } ,BCV[J;B5FDITXJGU

Slide 11

Slide 11 text

class APIClient { func sendRequest(_ request: T, completion: @escaping (Result) -> Void) { Alamofire.request(request.URL, method: request.method, parameters: request.parameters, headers: request.headers).responseData { response in switch response.result { case .success(let value): guard let responseData = request.responseFromObject(value) else { return completion(Result.failure(/* fail to parse error */)) } return completion(Result.success(responseData)) case .failure(let error): return completion(Result.failure(error)) } } } } ,BCV[J;B5FDITXJGU 䒷侧ך$MPTVSFָꟼ侧ךأ؝٦ف׾馄ִג׮ 㶷㖈ׅ׷㜥さ!FTDBQJOH׾אֽ׷ 4XJGU

Slide 12

Slide 12 text

struct GetUserRequest: RequestType { typealias ResponseType = User let id: String var path: String { return "/v1/user/\(self.id)" } var method: HTTPMethod { return .get } func responseFromObject(_ object: Data) -> User? { return User(object) } } ,BCV[J;B5FDITXJGU

Slide 13

Slide 13 text

APIClient().sendRequest(GetUserRequest(id: "id")) { result in switch result { case .success(let user): /* success handler */ break case .failure(let error): /* error handler */ break } } ,BCV[J;B5FDITXJGU

Slide 14

Slide 14 text

.PEFSO/FUXPSLJOHJO4XJGU "1*,JUIUUQTHJUIVCDPNJTILBXB"1*,JU 4XJTIIUUQTHJUIVCDPNUIPVHIUCPU4XJTI

Slide 15

Slide 15 text

Slide 16

Slide 16 text

Slide 17

Slide 17 text

Slide 18

Slide 18 text

"1*$MJFOU׾ぐِ٦أ؛٦أ חさ׻ׇג䭁䓸ׅ׷ ս劤겗վ

Slide 19

Slide 19 text

6TF$BTF (FU6TFS3FRVFTU (FU"OOPVODFNFOU3FRVFTU "1* ٍؗحءُ堣腉׾䭯אؙٔؒأزה䭯׋זְؙٔؒأز "1* DBDIF

Slide 20

Slide 20 text

protocol RequestCacheable { associatedtype ResponseType /** * キャッシュ有効フラグ */ var cacheEnabled: Bool { get } /** * キャッシュが存在する場合、ResponseTypeを返す */ func cache() -> ResponseType? } ,BCV[J;B5FDITXJGU

Slide 21

Slide 21 text

struct GetUserRequest: RequestType, RequestCacheable { typealias ResponseType = User let id: String var cacheEnabled = true var path: String { return "/v1/user/\(self.id)" } var method: HTTPMethod { return .get } func responseFromObject(_ object: Data) -> User? { return User(object) } func cache() -> User? { /* Realmなどを利用する */ return CacheManger.shared.fetch(self.id) as? User } } ,BCV[J;B5FDITXJGU

Slide 22

Slide 22 text

class APIClient { private func performRequest(_ request: T, completion: @escaping (Result) -> Void) { /* Alamofireでリクエストを送る */ } func sendRequest(_ request: T, completion: @escaping (Result) -> Void) { self.performRequest(request, completion: completion) } func sendRequest(_ request: T, completion: @escaping (Result) -> Void) { if request.cacheEnabled { if let data = request.cache() { return completion(Result.success(data)) } } self.performRequest(request, completion: completion) } } ,BCV[J;B5FDITXJGU

Slide 23

Slide 23 text

class APIClient { private func performRequest(_ request: T, completion: @escaping (Result) -> Void) { /* Alamofireでリクエストを送る */ } func sendRequest(_ request: T, completion: @escaping (Result) -> Void) { self.performRequest(request, completion: completion) } func sendRequest(_ request: T, completion: @escaping (Result) -> Void) { if request.cacheEnabled { if let data = request.cache() { return completion(Result.success(data)) } } self.performRequest(request, completion: completion) } } ,BCV[J;B5FDITXJGU 1SPUPDPM$PNQPTJUJPOח״׷5ZQF$POTUSBJOT 4XJGU

Slide 24

Slide 24 text

class APIClient { private func performRequest(_ request: T, completion: @escaping (Result) -> Void) { /* Alamofireでリクエストを送る */ } func sendRequest(_ request: T, completion: @escaping (Result) -> Void) { self.performRequest(request, completion: completion) } func sendRequest(_ request: T, completion: @escaping (Result) -> Void) { if request.cacheEnabled { if let data = request.cache() { return completion(Result.success(data)) } } self.performRequest(request, completion: completion) } } ,BCV[J;B5FDITXJGU ٍؗحءָُ㶷㖈ׅ׸לծ⽯䏟ח3FUVSO

Slide 25

Slide 25 text

APIClient().sendRequest(GetUserRequest(id: "id", cacheEnabled: true)) { result in switch result { case .success(let user): /* success handler */ break case .failure(let error): /* error handler */ break } } ,BCV[J;B5FDITXJGU

Slide 26

Slide 26 text

"1*$MJFOUXJUI$BDIF'FBUVSF (FU6TFS3FRVFTU (FU"OOPVODFNFOU3FRVFTU 3FRVFTU5ZQF 3FRVFTU5ZQF 3FRVFTU$BDIFBCMF JNQMFNFOUT

Slide 27

Slide 27 text

6TF$BTF (FU,FZXPSE-JTU3FRVFTU DBDIF "1* GBJM ♶㸜㹀ז㢩鿇"1*׾⢪欽׃גֶ׶ծٔزٓ؎׾⟣䠐ך㔐侧ד遤ְ׋ְ GBJM (FU,FZXPSE-JTU3FRVFTU "1* DBDIF 5IJSE"1* 5IJSE"1* 3FUSZ GBJM

Slide 28

Slide 28 text

protocol RequestRetryable { /** * リトライする回数 */ var retryCount: Int { get } /** * リトライするかどうかを判定する */ func canRetry(error: Error) -> Bool } extension RequestRetryable { var retryCount: Int { return 3 } } ,BCV[J;B5FDITXJGU

Slide 29

Slide 29 text

struct GetKeywordListRequest: RequestType, RequestCacheable, RequestRetryable { typealias ResponseType = KeywordList var cacheEnabled: Bool = false var retryCount: Int { return 4 } var path: String { return "/v1/keywords" } var method: HTTPMethod { return .get } func responseFromObject(_ object: Data) -> KeywordList? { return KeywordList(object) } func canRetry(error: Error) -> Bool { return /* conditions of retry */ ? true : false } func cache() -> KeywordList? { return CacheManger.shared.fetch("keywords") as? KeywordList } } ,BCV[J;B5FDITXJGU

Slide 30

Slide 30 text

class APIClient { private func performRetryableRequest( _ request: T, retryCounter: Int = 0, completion: @escaping (Result) -> Void) { Alamofire.request(request.URL, method: request.method, parameters: request.parameters, headers: request.headers).responseData { response in switch response.result { case .success(let value): guard let responseData = request.responseFromObject(value) else { return completion(Result.failure(/* fail to parse error */)) } return completion(Result.success(responseData)) case .failure(let error): if request.retryCount > retryCounter && request.canRetry(error: error) { return self.performRetryableRequest(request, retryCounter: retryCounter + 1, completion: completion) } return completion(Result.failure(error)) } } } ... ,BCV[J;B5FDITXJGU

Slide 31

Slide 31 text

class APIClient { private func performRetryableRequest( _ request: T, retryCounter: Int = 0, completion: @escaping (Result) -> Void) { Alamofire.request(request.URL, method: request.method, parameters: request.parameters, headers: request.headers).responseData { response in switch response.result { case .success(let value): guard let responseData = request.responseFromObject(value) else { return completion(Result.failure(/* fail to parse error */)) } return completion(Result.success(responseData)) case .failure(let error): if request.retryCount > retryCounter && request.canRetry(error: error) { return self.performRetryableRequest(request, retryCounter: retryCounter + 1, completion: completion) } return completion(Result.failure(error)) } } } ... ,BCV[J;B5FDITXJGU ؙٔؒأز׾ٔزٓ؎ׅ׷

Slide 32

Slide 32 text

class APIClient { ... private func performCachedRequest( _ request: T) -> T.ResponseType? { guard request.cacheEnabled else { return nil } return request.cache() } ... ,BCV[J;B5FDITXJGU

Slide 33

Slide 33 text

class APIClient { ... func sendRequest(_ request: T, completion: @escaping (Result) -> Void) { /* performRequest */ } func sendRequest(_ request: T, completion: @escaping (Result) -> Void) { /* performCachedRequest & performRequest */ } func sendRequest(_ request: T, completion: @escaping (Result) -> Void) { /* performRetryableRequest */ } func sendRequest(_ request: T, completion: @escaping (Result) -> Void) { /* performCachedRequest & performRetryableRequest */ } } ,BCV[J;B5FDITXJGU

Slide 34

Slide 34 text

APIClient().sendRequest(GetKeywordListRequest()) { result in switch result { case .success(let keywordList): /* success handler */ break case .failure(let error): /* error handler */ break } } ,BCV[J;B5FDITXJGU

Slide 35

Slide 35 text

"1*$MJFOUXJUI3FUSZ'FBUVSF (FU,FZXPSE-JTU3FRVFTU (FU"OOPVODFNFOU3FRVFTU 3FRVFTU5ZQF 3FRVFTU5ZQF 3FRVFTU$BDIFBCMF 3FRVFTU3FUSZBCMF (FU6TFS3FRVFTU 3FRVFTU5ZQF 3FRVFTU$BDIFBCMF JNQMFNFOUT

Slide 36

Slide 36 text

"EWBOUBHFT ぐؙٔؒأز嫣ח䗳銲ז堣腉׾➰♷ׅ׷ֿהָדֹ׷ "1*$MJFOU׾⢪ֲ⩎6TFS$PEFַ׵䠐陎ׅ׷ֿהָ㼰זְ

Slide 37

Slide 37 text

%JTBEWBOUBHFT ؙٔؒأزח➰♷ׅ׷堣腉ָ㢳ֻז׷הؕؔأחז׷ O⦐ך堣腉׾ׅץג笨繎ׅ׷ךחOךػة٦ٝ׾剅ֻ䗳銲ָ֮׷

Slide 38

Slide 38 text

5IBOLZPVGPSZPVSMJTUFOJOH