项目中使用的网络库为 Alamofire
,数据映射库为 ObjectMapper
,由于项目中存在的网络库使用不够友好,维护扩展新的接口比较繁琐,所以抽空重写了一个网络库的封装
基本框架方案的选择 旧的网络库框架使用了单例与扩展的模式,在使用中的形式为 API.requestA
、API.requestA
的形式,随着接口的增多,API
类下的方法日益增多,显得一团乱。所以接口的调用形式改为 Protocol
,哪里使用哪个模块的接口,就遵循对应模块的 API 协议,不再暴露所有API接口在整个项目中。
一般在业务调用接口的时候,希望处理数据的地方也是在当前位置(当然异步的回调放哪里都一样),这样的代码在后续的维护中更好找到业务逻辑,所以回调统一使用闭包,放弃 Target Select
模式。
基础请求 protocol 的创建 我们以最基础的 HTTP 请求举例
1 2 3 4 protocol API { func request() }
由于这是总的请求入口,那么需要传入各个请求需要使用的参数,这些又是跟业务相关的东西,那么应该封装在一起,这里传入的参数,只需要能获取到对应需要的各个值,不限定传入的参数类型,那么请求参数也设计成一个 Protocol
然后超时时间,对于这个参数,可能同一个接口在不同位置所需的时间会不一致,所以作为一个保留参数,提供默认值(由于协议的声明不是设置默认值,所以放在 extension
中),现在代码看起来是这样:
1 2 3 protocol API { func request(config: ApiConfigProtocol, timeoutInterval: TimeInterval) }
我们还需要集成数据映射,在请求返回数据初步处理之后,那么还需要一个泛型,因为映射的方法也属于一个协议,所以我们需要泛型遵循此协议,请求返回的闭包,参考 Alamofire
的方式,也以返回一个对象的形式来实现串行闭包:
1 2 3 4 5 protocol API { func request<T: APIMappable>( config: ApiConfigProtocol, timeoutInterval: TimeInterval) -> Response<T> }
现在所有请求需要和返回的要素有了,我们开始构建
构建 APIMappable
这里我们自定义一个 protocol
,基于 ObjectMapper
的 BaseMappable
协议
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 protocol APIMappable: BaseMappable { init?(json: Any?) } extension APIMappable { public init?(map: Map) { self.init(json: nil) } init?(json: Any?) { guard let JSON = json as? [String: Any], let obj: Self = Mapper().map(JSON: JSON) else { return nil } self = obj } } extension Array: APIMappable, BaseMappable where Element: BaseMappable { public mutating func mapping(map: ObjectMapper.Map) {} init?(json: Any?) { if let JSONArray = json as? [[String: Any]] { let obj: [Element] = Mapper(context: nil).mapArray(JSONArray: JSONArray) self = obj } else { return nil } } }
构建 ApiConfigProtocol
域名与header一般来说比较固定,就直接在extension
中返回默认值,它看起来像这样:
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 protocol ApiConfigProtocol { /// 域名 var domain: String {get} /// 路径 var path: String {get} /// 请求方法 var method: HTTPMethod {get} /// 请求头 var header: HTTPHeaders { get } /// 请求参数 var parameters: [String: Any]? { get } /// 参数编码 var encoder: ParameterEncoding {get} } extension ApiConfigProtocol { var domain: String { "https://xxxx.com" } var header: HTTPHeaders { defaultHeader() } func defaultHeader() -> HTTPHeaders { var dic = [String: String]() dic["token"] = "xxxx" return HTTPHeaders.init(dic) } }
在实际使用中,对应的业务模块实现该协议,例如一个与服务系统相关的模块:
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 40 41 42 43 enum APISystemConfig: ApiConfigProtocol { case sysTime case sysVersion var path: String { switch self { case .sysTime: return "/sysTime" case .sysVersion: return "/sysVersion" } } var header: HTTPHeaders { var defaultHeader = defaultHeader() switch self { case .sysTime: return defaultHeader case .sysVersion: defaultHeader.add(name: "platform", value: "ios") return defaultHeader } } var method: HTTPMethod { switch self { case .sysTime: return .get case .sysVersion: return .post } } var parameters: [String : Any]? { switch self { case .sysTime: return [:] case .sysVersion: return ["appVersion": "1.0.0"] } } var encoder: ParameterEncoding { switch self { case .sysTime, .sysVersion: return JSONEncoding.default } } }
构建 Response 此类需要管理众多闭包,以便于接口返回后的处理:
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 class Response<T: APIMappable> { init() {} weak var request: DataRequest? func cancel() { request?.cancel() } typealias successBlock = (T) -> Void typealias errorBlock = (APIError) -> Void private var successBlocks = [successBlock?]() private var errorBlocks = [errorBlock?]() fileprivate func postSuccess(_ model: T) { successBlocks.forEach {$0?(model)} } fileprivate func postError(_ error: APIError) { errorBlocks.forEach {$0?(error)} } @discardableResult func success(_ completionHandler: successBlock?) -> Self { modelBlocks.append(completionHandler) return self } @discardableResult func error(_ completionHandler: errorBlock?) -> Self { errorBlocks.append(completionHandler) return self } }
搭建Model框架 假如我们的服务器数据为
1 2 3 4 5 6 7 8 { "code": 200, "message": "no message", "data": { "time": 2942798372937 } }
data
是我们想要的业务数据,code
和 message
是用来处理接口的返回逻辑的,我们的 Response Model为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class ResponseModel: APIMappable { var code = 0 var message = "" var data: Any? = nil func mapping(map: ObjectMapper.Map) { code <- map["code"] message <- map["message"] data <- map["data"] } } class SysTimeModel: APIMappable { var time: TimeInterval = 0 func mapping(map: ObjectMapper.Map) { time <- map["time"] } }
实现请求逻辑 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 40 41 42 43 44 45 46 47 48 extension API { func request<T: APIMappable>( config: ApiConfigProtocol, timeoutInterval: TimeInterval = 20) -> Response<T> { let result = Response<T>() let request = AF.request(config.domain + config.path, method: config.method, parameters: config.parameters, encoding: config.encoder, headers: config.header ) {$0.timeoutInterval = timeoutInterval} request.responseData { res in switch res.result { case .success(let data): if let JSON = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments), let model = ResponseModel(json: JSON) { switch model.code { case 200: if let model = T.init(json: JSON) { result.postSuccess(model) } else { result.postError(APIError(message: "解析错误 \(model.message)")) } case 401: print("token失效") // loginOut() default: result.postError(APIError(message: "未知错误 \(model.message)")) } } else { result.postError(APIError(message: "JSON解析错误")) } case .failure(let error): result.postError(APIError(message: error.localizedDescription)) } } result.request = request return result } }
封装业务 1 2 3 4 5 6 7 8 9 10 11 protocol SystemAPI: API {} extension SystemAPI { func getSysTime() -> Response<ResponseModel> { return request(config: APISystemConfig.sysTime) } func getSysVersion() -> Response<[ResponseModel]> { return request(config: APISystemConfig.sysVersion) } }
使用 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 extension ViewController: SystemAPI { func getData() { getSysTime() .success { model in print("model", model.time) } .success { model in print("this model is", model) } .error { err in print("err", err.message) } let getSysVersionTask = getSysVersion() getSysVersionTask.success { list in list.forEach {print("list model _", $0.time)} } getSysVersionTask.error { err in } getSysVersionTask.cancel() } }