使用 protocol 优雅处理 API

作者: 不知其名的Edward | 来源:发表于2016-10-11 16:00 被阅读371次

    最近在迁移到 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
    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 层这样独立拆开后,测试起来也是非常方便的。

    相关文章

      网友评论

      本文标题:使用 protocol 优雅处理 API

      本文链接:https://www.haomeiwen.com/subject/krdbyttx.html