swiftiOS Swift && Objective-CiOS 实用技术

使用 protocol 优雅处理 API

2016-10-11  本文已影响371人  不知其名的Edward

最近在迁移到 Swift3.0 过程中,为了逐步将 AFNetworking 转移到 Alamofire 上,对于部分老的 OC 代码顺便一起做了重构,遂对于如何更好的组织 API 有了很多想法。

Why

在人员流动与 Swift 的版本更新过程中,项目中逐渐有了以下对网络操作的方式:

于是乎,同一个 API 可能有四种+的出现方式,加上同一个接口在不同服务中的调用,简直令人奔溃。

可以复用的东西当然要抽象出来,But,How?

How

Alamofire 4.0 版本中有很多变化: Request又进一步添加了 4 个子类 DataRequest, DownloadRequest, UploadRequest 以及 StreamRequest
pathmethod 对换了一个位置,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 层这样独立拆开后,测试起来也是非常方便的。

上一篇下一篇

猜你喜欢

热点阅读