最近在遷移到 Swift3.0 過(guò)程中撞芍,為了逐步將 AFNetworking
轉(zhuǎn)移到 Alamofire
上,對(duì)于部分老的 OC 代碼順便一起做了重構(gòu)具垫,遂對(duì)于如何更好的組織 API 有了很多想法趟卸。
Why
在人員流動(dòng)與 Swift 的版本更新過(guò)程中,項(xiàng)目中逐漸有了以下對(duì)網(wǎng)絡(luò)操作的方式:
- 基于 AFNetworking 的 OC 封裝
- 基于 AFNetworking 的 Swift 封裝
- 基于 Alamofire 的直接調(diào)用
- 基于 Alamofire 的 Swift 封裝
于是乎峦筒,同一個(gè) API 可能有四種+的出現(xiàn)方式究西,加上同一個(gè)接口在不同服務(wù)中的調(diào)用,簡(jiǎn)直令人奔潰物喷。
可以復(fù)用的東西當(dāng)然要抽象出來(lái)卤材,But,How峦失?
How
Alamofire 4.0 版本中有很多變化: Request
又進(jìn)一步添加了 4 個(gè)子類(lèi) DataRequest
, DownloadRequest
, UploadRequest
以及 StreamRequest
扇丛,
path
與 method
對(duì)換了一個(gè)位置,ParameterEncoding
由枚舉變成了協(xié)議尉辑。于是乎原來(lái)基于 Alamofire 的直接調(diào)用的就全掛了帆精,修改的工程量浩大。所以隧魄,我選擇基于 Alamofire 的 Swift 封裝重新來(lái)組織 API卓练。
Swift 推薦你使用更多的值類(lèi)型,但有時(shí)候會(huì)沒(méi)有對(duì)象那么靈活购啄,基于我們的業(yè)務(wù)邏輯襟企,我選擇如下的 protocol ,具體的實(shí)現(xiàn)可以選擇
protocol APIType {
var baseURL: String { get }
var path: String { get }
var url: String { get }
var method: HTTPMethod { get }
var parameters: Parameters { get }
var encoding: ParameterEncoding { get }
var headers: [String : String] { get }
}
extension APIType {
var url: String { return baseURL + path }
var parameters: Parameters { return Parameters() }
var encoding: ParameterEncoding { return URLEncoding() }
var headers: [String : String] { return [:] }
}
由于 API 來(lái)自幾臺(tái)不同的服務(wù)器闸溃,同組的 API 有著相似的行為模式和錯(cuò)誤處理方式整吆,在這里定義好 baseURL
, 同時(shí)也可以在這一層控制線(xiàn)上與線(xiàn)下環(huán)境,
protocol AnotherenAPIType: APIType { }
extension AnotherenAPIType {
var baseURL: String {
if EnvironmentManager.isOnline {
return "https://release.anotheren.com"
} else {
return "https://debug.anotheren.com"
}
}
}
為了方便調(diào)用辉川,使用一個(gè)收集組來(lái)收集所有的同組 API
struct AnotherenAPI { }
extension AnotherenAPI {
struct Login: AnotherenAPIType {
let userName: String
let password: String
init(userName: String, password: String) {
self.userName = userName
self.password = password
}
var path: String { return "/login" }
var method: HTTPMethod { return .post }
var parameters: Parameters {
return ["userName" : userName,
"password" : password]
}
}
}
Advance
實(shí)際上對(duì) API 返回?cái)?shù)據(jù)的處理也是固定的表蝙,比如這個(gè)接口是按照 JSON 格式返回?cái)?shù)據(jù)的,那其實(shí)就可以直接把數(shù)據(jù)處理后再返回乓旗,此處定義一個(gè) associatedtype ResultType
讓具體 API 定義時(shí)再確定 ResultType
的實(shí)際類(lèi)型
protocol JSONAPIType: APIType {
associatedtype ResultType
func handleJSON(json: JSON) -> Alamofire.Result<ResultType>
}
extension AnotherenAPI.Login: JSONAPIType {
func handleJSON(json: JSON) -> Result<LoginInfo> {
...
}
}
最后具體使用的時(shí)候就可以這樣處理府蛇,在回調(diào)的閉包中就可以直接拿到請(qǐng)求的結(jié)果 ResultType
,類(lèi)似的屿愚,當(dāng)接口返回全是圖片的時(shí)候也可以再增加 PictureAPIType
協(xié)議汇跨,并讓相關(guān)接口實(shí)現(xiàn)即可务荆。
func request(_ api: APIType) -> DataRequest {
let fullParameters = appendCustom(parameters: api.parameters)
let fullHeaders = appendCustom(headers: api.headers)
return Alamofire.request(api.url, method: api.method, parameters: fullParameters, encoding: api.encoding, headers: fullHeaders)
}
func requestJSON<T: JSONAPIType>(_ api: T, _ completionHandler: @escaping (Result<T.ResultType>) -> Void) -> DataRequest {
return request(api).responseSwiftyJSON({ result in
switch result {
case .success(let json):
completionHandler(api.handleJSON(json: json))
case .failure(let error):
completionHandler(Result.failure(error))
}
})
}
實(shí)際上,當(dāng)把 API 層這樣獨(dú)立拆開(kāi)后穷遂,測(cè)試起來(lái)也是非常方便的函匕。