最近在迁移到 Swift3.0 过程中,为了逐步将 AFNetworking
转移到 Alamofire
上,对于部分老的 OC 代码顺便一起做了重构,遂对于如何更好的组织 API 有了很多想法。
Why
在人员流动与 Swift 的版本更新过程中,项目中逐渐有了以下对网络操作的方式:
- 基于 AFNetworking 的 OC 封装
- 基于 AFNetworking 的 Swift 封装
- 基于 Alamofire 的直接调用
- 基于 Alamofire 的 Swift 封装
于是乎,同一个 API 可能有四种+的出现方式,加上同一个接口在不同服务中的调用,简直令人奔溃。
可以复用的东西当然要抽象出来,But,How?
How
Alamofire 4.0 版本中有很多变化: Request
又进一步添加了 4 个子类 DataRequest
, DownloadRequest
, UploadRequest
以及 StreamRequest
,
path
与 method
对换了一个位置,ParameterEncoding
由枚举变成了协议。于是乎原来基于 Alamofire 的直接调用的就全挂了,修改的工程量浩大。所以,我选择基于 Alamofire 的 Swift 封装重新来组织 API。
Swift 推荐你使用更多的值类型,但有时候会没有对象那么灵活,基于我们的业务逻辑,我选择如下的 protocol ,具体的实现可以选择
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 来自几台不同的服务器,同组的 API 有着相似的行为模式和错误处理方式,在这里定义好 baseURL
, 同时也可以在这一层控制线上与线下环境,
protocol AnotherenAPIType: APIType { }
extension AnotherenAPIType {
var baseURL: String {
if EnvironmentManager.isOnline {
return "https://release.anotheren.com"
} else {
return "https://debug.anotheren.com"
}
}
}
为了方便调用,使用一个收集组来收集所有的同组 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
实际上对 API 返回数据的处理也是固定的,比如这个接口是按照 JSON 格式返回数据的,那其实就可以直接把数据处理后再返回,此处定义一个 associatedtype ResultType
让具体 API 定义时再确定 ResultType
的实际类型
protocol JSONAPIType: APIType {
associatedtype ResultType
func handleJSON(json: JSON) -> Alamofire.Result<ResultType>
}
extension AnotherenAPI.Login: JSONAPIType {
func handleJSON(json: JSON) -> Result<LoginInfo> {
...
}
}
最后具体使用的时候就可以这样处理,在回调的闭包中就可以直接拿到请求的结果 ResultType
,类似的,当接口返回全是图片的时候也可以再增加 PictureAPIType
协议,并让相关接口实现即可。
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))
}
})
}
实际上,当把 API 层这样独立拆开后,测试起来也是非常方便的。