Alamofire – APIRouter
2020/6 更新
在 iPlayground2019 聽完王巍大大的 talk 之後,又自己實作了三次,已經放棄下面的封裝方式
範例在 Github,因為實作概念基本上等同王巍大大的 Talk(多了一些參考自 Moya 的概念)所以等 哪天心情好 有空,再來寫一篇文章好了哈哈哈哈哈。
# 起
Alamofire 相信開發 iOS 的人都不陌生,就算沒用過也聽,非常好用的一個第三方網路處理套件,網路上也有非常多關於 Alamofire 的封裝,google alamofire router 的教學文章也是一大堆。
大部分的文章看起來都會像這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
enum APIRouter { case fetchData case uploadData case deleteData(String) var method: HTTPMethod { switch self { case .fetchData: return .get case .uploadData: return .post case .deleteData: return .post } } var path: String { switch self { case .fetchData: return "/data/list" case .uploadData: return "/data/uplod" case .deleteData: return "/data/delete" } } var parameters: Parameters? { switch self { case .fetchData: return ["time": DataLastSyncTime] case .uploadData: return ["data": Data] case .deleteData(let id): return ["id": id] } } func asURLRequest() throws -> URLRequest { //... retutn ... } } |
# 承
假設今天我們是的 APP 只有需要呼叫 十幾支 API
,這樣的寫法或許感覺不到什麼問題,但是當 API 的數量增加到 幾十支
的時候,就會發現整個 router 非常的混亂,每次增加一支 API ,就需要把整個檔案重頭滾動到尾,一個 http request 的 URL、http method、 parameters,散落在檔案的各處,不管是新增還是修改都極為麻煩。
# 轉
後來在網路上發現了一個更好的寫法,但原文找不到了,後來我憑著印象重現了當初看到的文章。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
struct APIRouter: URLRequestConvertible { let path: String let method: HTTPMethod var parameters: Parameters? func asURLRequest() throws -> URLRequest { let url = URL(string: "https://www.google.com")! .appendingPathComponent(path) var urlRequest = URLRequest(url: url) urlRequest.httpMethod = method.rawValue urlRequest.timeoutInterval = 30 // Http common header urlRequest.allHTTPHeaderFields = ESHTTPHeaders.default // add custom do { return try JSONEncoding.default.encode(urlRequest, with: parameters) } catch { fatalError("\(error)") } } } extension APIRouter { static func login(requestModel model: LoginRequestModel) -> APIRouter { return APIRouter(path: "/login", method: .post, parameters: model.convertToParameter()) } } |
這邊應該是使用了 工廠模式(factory pattern)
我把 enum 改成了 struct ,然後一樣 confirm URLRequestConvertible
,並實作 asURLRequest() ,然後使用 static func create 這個 struct。
其中最後的 model.convertToParameter()
是自己宣告 & 實作的 protocol
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
protocol ParameterConvertible { func convertToParameter() -> Parameters? } extension ParameterConvertible where Self: Encodable { func convertToParameter() -> Parameters? { do { let data = try JSONEncoder().encode(self) return try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] } catch { fatalError("\(error)") } } } |
假設 model 沒有特殊的變化,就可以使用預設的 func convertToParameter()
,如果今天 model 與 api 的參數對應不起來,也可以 model 自己實作這個 func 來客製參數的結構。
# 合
所以我們把一個 http request 的 URL、http method、 parameters… 的資訊,通通寫在一個 func 了,是不是清楚許多R。