添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

Repository files navigation

CSAPIService

CSAPIService 是一个轻量的 Swift 网络抽象层框架,将请求、解析等流程工作分成几大角色去承担,完全面向协议实现,利于扩展。

pod 'CSAPIService'

其实原来的名称为 APIService ,但是因为该名在 CocoaPods 已经被占用了,就加了前缀,但是在使用时,模块名称依然是 APIService

代码注释比较完备,部分细节可以直接查看代码。

  • 层次清晰,按需引入;
  • 支持Request级别拦截器,并支持对响应进行异步替换;
  • 支持插件化拦截器;
  • 面向协议,角色清晰,方便功能扩展;
  • 缓存可拆卸,缓存包含内存、磁盘两级缓存;
  • 箭头指的是发送流程,实心点指的是数据回调流程; 高清图可见 链接

    框架按照网络请求流程中涉及的步骤将其内部分为几个角色:

  • 请求者(APIRequest):更准确的叫法应该是 构建请求者 ,其主要作用就是将外部传入的相关参数经过相关处理构造成一个 URLRequest 实例,并且提供请求拦截器以及回调拦截器两种拦截器;
  • 发送者(APIClient):实际的网络请求发送者,目前默认是 Alamofire ,你也可以实现协议,灵活的对发送者进行替换;
  • 解析者(APIParsable):一般与 Model 是同一个角色,由 Model 实现协议从而实现从数据到实体这一过程的映射;
  • 缓存(APICache、APICacheTool):缓存相关;
  • 服务提供者(APIService):整个框架的服务提供者,提供最外层的 API ,可以传入插件;
  • 整个框架是按照 POP 的思想进行设计,将相关角色都尽量抽象成协议,方便扩展;

    APIRequest 协议

    描述一个 URLRequest 实例需要的信息,并提供相应的拦截服务,在构造中我们可以设置返回的 ResponseModel 类型;

    我们应当以 领域服务 为单位来提供对应的 APIRequest 领域服务 大部分会按照域名不同来划分,即 A 域名对应 AAPIRequest ,B 域名对应 BAPIRequest

    APIRequest 的拦截器应用场景主要是整个 领域服务 级别的,一般添加的逻辑都是统一的逻辑。如:

  • 发送前加上统一参数,Header 等信息;
  • 数据回调到业务之前统一对一些 code 进行判断,如未登录自动弹出登录框等统一逻辑;
  • /// 拦截参数,在参数编码之前
    func intercept(parameters: [String: Any]?) -> [String: Any]?
    /// 请求发送之前
    func intercept(urlRequest: URLRequest) throws -> URLRequest
    /// 数据回调给业务之前
    /// 利用 replaceResponseHandler 我们可以替换返回给业务的数据,还可以用作一些重试机制上等;
    /// 需要注意的是一旦实现该方法,需要及时使用 replaceResponseHandler 将 response 返回给业务方。
    func intercept<U: APIRequest>(request: U, response: APIResponse<Response>, replaceResponseHandler: @escaping APICompletionHandler<Response>)

    APICache

    APICache 是一个 struct ,用来配置一个API请求时的 缓存 设置,相关设置包括:

    相关属性作用请看注释。

    public struct APICache {
        public init() { }
        /// 读取缓存模式
        public var usageMode: APICacheUsageMode = .none
        /// 写入缓存模式
        public var writeNode: APICacheWriteMode = .none
        /// 只有 writeNode 不为 .none 时,后面参数有效
        /// 额外的缓存key部分
        /// 可添加app版本号、用户id、缓存版本等
        public var extraCacheKey = ""
        /// 自定义缓存key
        public var customCacheKeyHandler: ((String) -> String)?
        /// 缓存过期策略类型
        public var expiry: APICacheExpiry = .seconds(0)
    public protocol APIRequest {
        // MARK: - 缓存相关
        /// 缓存
        /// 目前 taskType 为 request 才生效
        var cache: APICache? { get }
        /// 是否允许缓存
        /// 可根据业务实际情况控制:比如业务code为成功,业务数据不为空
        /// 这个闭包之所以不放入 APICache 内部的原因是 享受泛型的回调
        var cacheShouldWriteHandler: ((APIResponse<Response>) -> Bool)? { get }
        /// 过滤不参与缓存key生成的参数
        /// 如果一些业务场景不想统一参数参与缓存key生成,可在此配置
        var cacheFilterParameters: [String] { get }
    

    底层的缓存实现是可以通过设置delegate的方式进行替换。

    /// 可以通过调整 cacheTool 的实现来更换缓存底层实现
    APIConfig.shared.cacheTool = CacheTool.shared

    框架内部对于缓存的读写采用的是 同步读,异步存 的方式。

    使用方式:

    enum HomeBannerAPI {
        struct HomeBannerRequest: CSAPIRequest {
            typealias DataResponse = HomeBanner
            var parameters: [String: Any]? {
                return nil
            var path: String {
                return "/config/homeBanner"
            /// 设置缓存
            var cache: APICache? {
                var cache = APICache()
                cache.readMode = .cancelNetwork
                cache.writeNode = .memoryAndDisk
                cache.expiry = .seconds(10)
                return cache
    let request = HomeBannerAPI.HomeBannerRequest()
    APIService.sendRequest(request, plugins: [networkActivityPlugin], cacheHandler: { response in
         /// 缓存回调
        switch response.result.validateResult {
            case let .success(info, _):
                debugPrint(info)
            case let .failure(_, error):
                debugPrint(error)
    }, completionHandler: { response in
        /// 网络回调
        switch response.result.validateResult {
            case let .success(info, _):
                debugPrint(info)
            case let .failure(_, error):
                debugPrint(error)
    

    我们可以看到,sendRequest 为缓存单独加了一个回调,而不是和原来的completionHandler使用同一个,目的是想让业务方可以明确的感知到该次回调是来自网络还是缓存,也是呼应 APICacheReadMode 这个配置。

    APIClient 协议

    负责发送一个Request请求,我们可以调整APIClient的实现方式; 目前默认实现方式为Alamofire,其中使用别名等方式做了隔离。

    /// 网络请求任务协议
    public protocol APIRequestTask {
        /// 恢复
        func resume()
        /// 取消
        func cancel()
    /// 网络请求客户端协议
    public protocol APIClient {
        func createDataRequest(
            request: URLRequest,
            progressHandler: APIProgressHandler?,
            completionHandler: @escaping APIDataResponseCompletionHandler
        ) -> APIRequestTask
        func createDownloadRequest(
            request: URLRequest,
            to: @escaping APIDownloadDestination,
            progressHandler: APIProgressHandler?,
            completionHandler: @escaping APIDownloadResponseCompletionHandler
        ) -> APIRequestTask
    

    APIClient 协议比较简单,就是根据请求类型区分不同的方法,其返回值也是一个协议(APIRequestTask),支持resume以及cancel操作。

    目前操作只保留数据请求、下载请求两种方式,其他方式后续版本再补充;

    APIParsable 协议

    public protocol APIParsable {
        static func parse(data: Data) throws -> Self
    

    如上所示,APIParsable协议其实很简单,实现者通常是Model,就是将Data类型的数据映射成实体类型。

    这是最上层的协议,在该协议下方目前还有APIJSONParsable协议,其继承了APIParsable协议,如下所示:

    public protocol APIJSONParsable: APIParsable {}
    extension APIJSONParsable where Self: Decodable {
        public static func parse(data: Data) throws -> Self {
            do {
                let model = try JSONDecoder().decode(self, from: data)
                return model
            } catch {
                throw APIResponseError.invalidParseResponse(error)
    

    目前协议的默认实现方式是通过Decodable的方式将JSON转为Model

    当然我们可以根据项目数据交换协议扩展对应的解析方式,如 XML、Protobuf等;

    public typealias APIDefaultJSONParsable = APIJSONParsable & Decodable

    同时为方便业务使用,添加了一个别名,如果使用默认方式 Decodable 进行解析,最外层 Model 就可以直接实现该协议。

    APIPlugin

    public protocol APIPlugin {
        /// 构造URLRequest
        func prepare<T: APIRequest>(_ request: URLRequest, targetRequest: T) -> URLRequest
        /// 发送之前
        func willSend<T: APIRequest>(_ request: URLRequest, targetRequest: T)
        /// 接收结果,时机在返回给调用方之前
        func willReceive<T: APIRequest>(_ result: APIResponse<T.Response>, targetRequest: T)
        /// 接收结果,时机在返回给调用方之后
        func didReceive<T: APIRequest>(_ result: APIResponse<T.Response>, targetRequest: T)
    

    在具体网络请求层次上提供的拦截器协议,这样业务使用过程中可以感知到请求请求中的重要节点,从而完成一些逻辑,如Loading的加载与消失就可以通过构造一些对应的实例去完成。

    目前提供了一个默认的 Plugin,是: NetworkActivityPlugin

    APIService

    这是最外层的供业务发起网络请求的API

    open class APIService {
        private let reachabilityManager = APINetworkReachabilityManager()
        public let client: APIClient
        public init(client: APIClient) {
            self.client = client
        /// 单例
        public static let `default` = APIService(client: AlamofireAPIClient())
    

    APIService提供类方法以及实例方法,其中类方法就是使用的default实例,当然也可以其他的APIClient实现实例然后调用实例方法,等后续对底层实现进行替换是,只需要替换default实例的默认实现就可以了。

    public func sendRequest<T: APIRequest>(
            _ request: T,
            plugins: [APIPlugin] = [],
            encoding: APIParameterEncoding? = nil,
            progressHandler: APIProgressHandler? = nil,
            completionHandler: @escaping APICompletionHandler<T.Response>
        ) -> APIRequestTask? { }

    实例方法定义如上,支持传入APIPlugin 实例数组。

    业务使用实践

    相关代码在 Demo 工程里面可以看到。

    最外层的 Model

    /// 网络请求结果最外层Model
    public protocol APIModelWrapper {
        associatedtype DataType: Decodable
        var code: Int { get }
        var msg: String { get }
        var data: DataType? { get }
    

    对于大多数的网络请求而言,拿到的回调结果最外层肯定是最基础的 Model,也就是所谓的BaseReponseBean,其字段主要也是codemsgdata

    我们定义这样的一个协议方便后续使用,其实这个协议当时是直接放在库里面的,后来发现库内部对其没有依赖,其更多是一种更优的编程实践,就提出来放到了 Demo 工程里面去。

    我们需要构造一个实体去实现该协议,

    public struct CSBaseResponseModel<T>: APIModelWrapper, APIDefaultJSONParsable where T: Decodable {
        public var code: Int
        public var msg: String
        public var data: T?
        enum CodingKeys: String, CodingKey {
            case code
            case msg = "desc"
            case data
    

    因为最外层的 CSBaseResponseModel 已经满足了 APIDefaultJSONParsable协议,所以业务Model不需要再实现该协议了,而是直接实现Decodable就好。

    有的小伙伴可能会想不能直接使用实体吗?为什么还需要一个协议,这个协议在后面会用到。

    业务 APIRequest

    /// 注意这个 CSBaseResponseModel
    protocol CSAPIRequest: APIRequest where Response == CSBaseResponseModel<DataResponse> {
        associatedtype DataResponse: Decodable
        var isMock: Bool { get }
    extension CSAPIRequest {
      	var isMock: Bool {
            return false
        var baseURL: URL {
            if isMock {
                return NetworkConstants.baseMockURL
            switch NetworkConstants.env {
            case .prod:
                return NetworkConstants.baseProdURL
            case .dev:
                return NetworkConstants.baseDevURL
        var method: APIRequestMethod { .get }
        var parameters: [String: Any]? {
            return nil
        var headers: APIRequestHeaders? {
            return nil
        var taskType: APIRequestTaskType {
            return .request
        var encoding: APIParameterEncoding {
            return APIURLEncoding.default
        public func intercept(urlRequest: URLRequest) throws -> URLRequest {
            /// 我们可以在这个位置添加统一的参数、header的信息;
            return urlRequest
        public func intercept<U: APIRequest>(request: U, response: APIResponse<Response>, replaceResponseHandler: @escaping APICompletionHandler<Response>) {
            /// 我们在这里位置可以处理统一的回调判断相关逻辑
            replaceResponseHandler(response)
    

    APIResult 扩展

    我们通过APIResult最终获得的是最外层的Model,那对于大部分业务方而言,他们拿到数据后还会有一些通用逻辑,如:

  • 根据code值判断请求是否成功;
  • 错误本地化;
  • 获取实际的data数据;
  • 而这些逻辑在每一个域名服务又可能是不同的,属于业务逻辑,所以不宜放入库内部。

    那对于这些逻辑,我们就可以对 APIResult 进行扩展,将这些逻辑收进去,业务方可以根据自己的需求决定在拿到APIResult之后是否还调用这个扩展。

    如果有多种逻辑,可以考虑增加一些特定前缀去区别,如下面的validateResult我们可以扩展为多个 -- csValidateResultcfValidateResult等等。

    public enum APIValidateResult<T> {
        case success(T, String)
        case failure(String, APIError)
    public enum CSDataError: Error {
        case invalidParseResponse
    /// APIModelWrapper 在这个地方用到了
    extension APIResult where T: APIModelWrapper {
        var validateResult: APIValidateResult<T.DataType> {
            var message = "出现错误,请稍后重试"
            switch self {
            case let .success(reponse):
                if reponse.code == 200, let data = reponse.data {
                    return .success(data, reponse.msg)
                } else {
                    return .failure(message, APIError.responseError(APIResponseError.invalidParseResponse(CSDataError.invalidParseResponse)))
            case let .failure(apiError):
                if apiError == APIError.networkError {
                    message = apiError.localizedDescription
                assertionFailure(apiError.localizedDescription)
                return .failure(message, apiError)
    

    基础使用方式

    enum HomeBannerAPI {
        struct HomeBannerRequest: CSAPIRequest {
            typealias DataResponse = HomeBanner
            var parameters: [String: Any]? {
                return nil
            var path: String {
                return "/config/homeBanner"
    APIService.sendRequest(HomeBannerAPI.HomeBannerRequest()) { response in
        switch response.result.validateResult {
        case let .success(info, _):
            /// 这个 Info 就是上面我们传入的 HomeBanner 类型
            print(info)
        case let .failure(_, error):
            print(error)
    

    项目示例中有两种使用形式,一种是 协议默认实现,一种是类继承,比较推荐协议默认实现,但是当有重试逻辑时,协议默认实现无法cover,增加了类继承方式;