Swift Moya如何高内聚低耦合?

471 阅读4分钟

前言

新项目使用了Moya框架处理网络请求,项目中对Moya的封装使得笔者不是很满意,因为所有的网络请求都在一个文件进行枚举,这样导致耦合性很大。这个框架之前没怎么接触过,在读完了Moya的官方文档后,笔者决定对Moya进行重新封装从而实现高内聚低耦合。

Moya介绍

在您的应用中,Moya从事于高层抽象的工作。你不应该直接引用Alamofire. 虽然它是一个很棒的库,但是Moya的观点是你不必处理那些低级的细节。

(如果你需要使用Alamofire, 你可以传递一个 SessionManager 对象实例给到 MoyaProvider 构造器.)

如果你想改变Moya的行为,库中可能已经有一种在不修改库的情况下来达到你的目的方法。Moya被设计的超级灵活且满足了每个开发者的需求. 它不是一个实现网络请求的编码性的框架(那是Alamofire的责任),更多的是关于如何考虑网络请求的框架。
Moya官方中文文档,有兴趣的同学可以去拜读一下

如何实现低耦合、高内聚

问题

使用过或者看过Moya文档推荐的demo也会发现,新增一个接口就要修改一次enum,笔者认为这样是不好的

目标

1.同一个模块或者同一个页面的接口可以内聚在一起
2.相同的地方可以抽取出来,当作公用依赖
3.不同模块的接口要独立分开
4.定义接口API和使用时要简单明了

下面我们将通过继承协议和泛型依次实现以上目标

实现

内聚问题

关于内聚的问题,Moya已经支持的很好了,同一个页面的多个接口定义,只需要对枚举进行扩展即可,我们保持该优点即可

抽取公共代码

公共代码主要是TargetType中公共且不变的部分,TargetType是一个协议,可以通过添加协议的默认实现来处理

import Foundation
import Moya

public protocol JZMoyaTargetType: TargetType {
    var parameters: [String: Any]? { get }
}

extension JZMoyaTargetType {
    
    public var baseURL: URL { URL(string: "https://xxx.xxx.xx")! }
    
    public var headers: [String: String]? {
        return ["Authorization":UserDataManager.manager.getAccountInfo()?.token ?? "",
                "Content-type": "application/json",
                "version_name":UIDevice.systemVersion(),
                "user_agent":"iOS," + UIDevice.systemVersion() + "," + UUIDTool.getUUID()]
    }
    
    public var method: Moya.Method {
        return .post
    }
    
    public var task: Task {
        let requestParameters = parameters ?? [:]
        let encoding: ParameterEncoding
        switch self.method {
        case .post:
            encoding = JSONEncoding.default
        case .get:
            encoding = URLEncoding.default
            
        default:
            encoding = JSONEncoding.default
        }
        return .requestParameters(parameters: requestParameters, encoding: encoding)
    }
    //发起请求
    func jz_request<T: BaseResponseModel>(_ t: T.Type,
                                          showErrorToast: Bool = false,
                                          successBlock: @escaping JZHTTPSuccessBlock<T>,
                                          failureBlock: JZHTTPFailureBlock? = nil) {
        guard NetworkMonitor.manager.isReachable == true else {
            Toast("网络异常~")
            return
        }
        
        let provider = MoyaProvider<Self>()
        provider.request(self) { result in
            
            switch result {
            case .success(let response):
                let jsonString = try? response.mapString()
                if response.statusCode == 200 {
                    let model = T.init(JSONString: jsonString ?? "")
                    if let _model = model {
                        successBlock(_model, jsonString)
                    }
                }else {
                    if showErrorToast, let msg = jsonString {
                        Toast(msg)
                    }
                    failureBlock?(NSError(domain: "", code: response.statusCode, userInfo: ["msg":jsonString as Any]))
                }
            case .failure(let error):
                if showErrorToast {
                    Toast("网络异常~")
                }
                failureBlock?(error)
            }
        }
    }
}

代码解释

笔者为了方便后续的扩展,这里定义了一个新的协议JZMoyaTargetType

  1. parameters:请求参数,这里新增是为了统一获取不同case的请求参数,方便request使用,由具体的API提供。
  2. baseURL:请求基地址
  3. headers:请求头这里不做过多介绍
  4. method:默认是post,如果是其他方式,可在具体API中实现该属性,从而覆盖掉默认实现
  5. task:通过method确认请求参数的编码格式,及请求方式
  6. jz_request:下面单独拎出来讲解

发起请求:jz_request

这里把发起网络请求放在公共模块是因为,请求请求参数、header、path、method都可以拿到。
因为不同的接口返回的内容不一样,所以这里需要对模型做泛型处理,将数据解析成具体的模型,回调给外部使用。此处的T 是模型类
注意
这里有一个大写的Self和一个小写的selfSelf指的是枚举类型,而self指的具体的case

解偶

前面把公共模块提取之后,现在需要支持自定义接口了,如请求路径、参数、方式等。下面是定义和使用接口API示例:

登录接口API

mport Foundation
import Moya

public enum JZLoginApi: JZMoyaTargetType {
    case login(String)
    
    public var path: String { return "/v1/account/login"  }
    
    public var parameters: [String: Any]? {
        switch self {
        case .login(let token):
            return ["token": token]
        }
    }
}

登录接口调用

let token = result["token"]
let api = JZLoginApi.login(token as! String)
api.jz_request(UserInfoModel.self, successBlock: { model, jsonString in
    guard let userInfoM = model.data else {
        return
    }
    UserDataManager.manager.updateAutoLoginInfo(model: userInfoM)
}, failureBlock: { error in
})

如果想在登录模块新增个接口

iimport Foundation
import Moya

public enum JZLoginApi: JZMoyaTargetType {
    case login(String)
    case autoLogin
    
    //请求路径
    public var path: String {
        switch self {
        case .autoLogin:
            return "/v1/account/umeng/login"
        case .login(_):
            return "/v1/account/autoLogin"
        }  
    }
    //参数
    public var parameters: [String: Any]? {
        switch self {
        case .autoLogin:
            return nil
        case .login(let token):
            return ["token": token]
        }
    }
    //出现方式不同时,需要指定区分
    public var method: Moya.Method {
        switch self {
        case .autoLogin:
            return .get
        case .login(_):
            return .post
        }
    }
}

首页的接口API

import Foundation
import Moya

public enum JZHomePageApi: JZMoyaTargetType {
    case homeList
    
    public var path: String { return "/v1/home/list"  }
    
    public var parameters: [String: Any]? {
        return nil
    }
}

总结

这样一个基于Moya 低耦合高内聚的网络框架就封装好了,由于笔者每天忙着赶需求,暂时先做这么一个简单的封装,等遇到相关需求时再进行扩展。有兴趣的小伙伴,也可以在此基础上进行扩展,比如上传下载、签名、日志记录、异常处理等。