Swift - 当Moya遇上RxSwift(网络架构优化)

8,102 阅读6分钟

一、Moya 面向协议网络编程

1️⃣:Moya初探

如果你上面的POP面向协议编程已经看得差不多了,那么这个模块内容是非常简单的!

常规网络层在iOS应用程序中很常见。它们不好有几个原因:

  • 让编写新的应用程序变得困难(“我从哪里开始?”)
  • 很难维护现有的应用程序(“哦,天哪,这一团糟……”)
  • 使编写单元测试变得困难(“我如何再做一次?”)

Moya 的基本思想是:我们需要一些网络抽象层,能够充分封装直接调用Alamofire。它应该足够简单,普通的事情很容易,但是足够全面,复杂的事情也很容易。

  • Moya 的特点有以下几点:
  • 编译时检查正确的API端点访问。
  • 允许您定义具有关联枚举值的不同端点的明确用法。
  • 将测试存根视为一等公民,因此单元测试非常容易。

2️⃣:Moya开发使用

1:接口枚举

public enum LGLoginAPI {
    case login(String, String, String)  // 登录接口
    case smscode(String)                // 登录,发送验证码
    case otherRequest                   // 其他接口,没有参数
}
  • 这个接口枚举提供这个登录注册模块的所有接口
extension LGLoginAPI: TargetType {
    //服务器地址
    public var baseURL: URL {
        return URL(string:"http://127.0.0.1:5000/")!
    }
    // 各个请求的具体路径
    public var path: String {
        switch self {
        case .login:
            return "login/"
        case .smscode:
            return "login/smscode/"
        case .otherRequest:
            return "login/otherRequest/"
        }
    }
    // 请求方式
    public var method: Moya.Method {
        switch self {
        case .login:
            return .post
        case .smscode:
            return .post
        default:
            return .get
        }
    }
    //这个就是做单元测试模拟的数据,只会在单元测试文件中有作用
    public var sampleData: Data {
        return "{}".data(using: String.Encoding.utf8)!
    }
    //请求任务事件(这里附带上参数)
    public var task: Task {
        var param:[String:Any] = [:]
        switch self {
        case .login(let username,let password,let smscode):
            param["username"] = username
            param["password"] = password
            param["smscode"] = smscode
        case .smscode(let username):
            param["username"] = username
        default:
            return .requestPlain
        }
        return .requestParameters(parameters: param, encoding: URLEncoding.default)
    }
    //设置请求头
    public var headers: [String: String]? {
        return nil
    }
}
  • baseURL:服务器地址host 处理
  • path:根据不同的接口,确定各个请求的具体路径
  • method:根据不同的接口,设置请求方式
  • headers:统一配置的请求头信息配置
  • task:配置内部参数,以及task信息

2:登录模块网络管理者

class LGLoginClient: NSObject {
    static let manager = LGLoginClient()
    
    //MARK: - 验证码事件
    func smscode(username:String,complete:@escaping ((String) -> Void)) {
        let provide = MoyaProvider<LGLoginAPI>()
        provide.request(.smscode(username)) { (result) in
            switch result{
            case let .success(response):
                let dict = LGLoginClient.lgJson(data: response.data)
                complete(dict["smscode"] as! String)
            case let .failure(error):
                print(error)
                complete("")
            }
        }
    }
}
  • MoyaProvider 是此次网络请求的信息提供者
  • MoyaProvider 根据模块 LGLoginAPI 设置的信息绑定数据请求
  • MoyaProvider 通过调用 request 方法传出此次请求的接口,但是参数需要应用层提供!
  • 获取回调信息,然后进行 json 序列化!
  • 最后利用函数式编程思想回调 携带信息的闭包 给应用层

3:应用层调用

@IBAction func didClickCodeBtn(_ sender: Any) {
    LGLoginClient.manager.smscode(username: username) { [weak self](smscode) in
        self?.smscodeTF.text = smscode
    }
}
  • 应用层只需要为此次网络提供信息参数
  • 在回调闭包拿到信息,处理其他业务就OK!

Moya模型总结:CFNextwork -> Alamofire -> Moya -> 业务层

3️⃣:Moya直接使用的弊端

  • 如果整个项目是 RxSwift(毕竟现在函数响应式编程已成为趋势),显然我们更需要序列,因为序列可以直接绑定响应UI,更便于开发!
  • 当前只是这么直接使用 Moya 对我们来说还缺了很严重的一步:模型化
  • 说白了我们的网络整个架构层的模块希望是下面这样的:

Moya模型总结:CFNextwork -> Alamofire -> Moya -> 模型化 -> RxSwift -> 业务层

二、RxMoya 序列化

为了能够增加 Moya 序列化的能力,做了 Reactive 拓展!

public extension Reactive where Base: MoyaProviderType {
/// Designated request-making method with progress.
    public func requestWithProgress(_ token: Base.Target, callbackQueue: DispatchQueue? = nil) -> Observable<ProgressResponse> {
        let progressBlock: (AnyObserver) -> (ProgressResponse) -> Void = { observer in
            return { progress in
                observer.onNext(progress)
            }
        }
        let response: Observable<ProgressResponse> = Observable.create { [weak base] observer in
            let cancellableToken = base?.request(token, callbackQueue: callbackQueue, progress: progressBlock(observer)) { result in
                switch result {
                case .success:
                    observer.onCompleted()
                case let .failure(error):
                    observer.onError(error)
                }
            }
            return Disposables.create {
                cancellableToken?.cancel()
            }
        }
}
  • 1:观察者发送响应网络进度的序列 observer.onNext(progress)
  • 2:提供成功或者失败的序列 observer.onCompleted()observer.onError(error)
  • 3:对这次 RxSwift 封装的销毁对外提供,外界随时随地断开响应关系!
  • 4:说白了也就是对 Moya 包装一层 RxSwift
  • 5:外界调用:MoyaProvider 初始化对象调用:rx.request(target)

三、RxMoya 模型化

毕竟我们开发人员更多的关注是模型,而非 data、抑或 json

extension PrimitiveSequence where TraitType == SingleTrait, Element == Moya.Response {

    func map<T: ImmutableMappable>(_ type: T.Type) -> PrimitiveSequence<TraitType, T> {
       return self
           .map { (response) -> T in
             let json = try JSON(data: response.data)
             guard let code = json[RESULT_CODE].int else { throw RequestError.noCodeKey }
             if code != StatusCode.success.rawValue { throw RequestError.sysError(statusCode:"\(code)" , errorMsg: json[RESULT_MESSAGE].string) }
            if let data = json[RESULT_DATA].dictionaryObject {
                return try Mapper<T>().map(JSON: data)
            }else if let data = json[RESULT_RESULT].dictionaryObject {
                return try Mapper<T>().map(JSON: data)
            }
             throw RequestError.noDataKey
        }.do(onSuccess: { (_) in
            
        }, onError: { (error) in
            if error is MapError {
                log.error(error)
            }
        })
    }
}
  • 首先拓展 PrimitiveSequence 实际对象是处理 Moya.Response
  • 通过调用 SwiftyJSONResponsedata 解析成 json
  • 然后调用 ObjectMapper 转成相应模型数据
  • 数组模型处理差不多,大家只要返回 [T] 就 OK

四、外界调用

loginService.login().asObservable()
    .subscribe(onNext: {[weak self] (rcmdBranchModel) in
        
        guard let `self` = self else { return }
        self.requestIds = rcmdBranchModel.tab.map{$0.id}
        self.menuTitles += rcmdBranchModel.tab.map{$0.name}
        self.pageController.magicView.reloadData(toPage: 1)
    })
    .disposed(by: disposeBag)
  • 清爽干净,耦合度大大降低,复用性大大提高!
  • 业务层只对业务需求负责

五、总结

1️⃣:响应下沉

  • 从业务层流出请求响应的必要条件
  • 模块层 LGLoginClient 接受业务层的响应及时处理条件交付给 MoyaProvider
  • MoyaProvider 也就是我们的 Moya 层调度集中管理继续下沉给 Alamofire
  • Alamofire 包装处理请求,交给 CFNextwork 去真正处理网络下层

2️⃣:回调上浮

  • CFNextwork 请求获得 Response 通过代理交付给 Alamofire
  • Alamofire 通过包装、验证、序列化回调给封装它的 Moya
  • Moya 这一层通过 SwiftyJSON 进行 json化,经过 ObjectMapper 进行 模型化
  • Moya 再经过 RxSwift 进行 序列化
  • 通过 模块网络层 回调出去相应的 模型序列 给应用层

3️⃣:小结: 一图胜言

整个流程构成一个闭环,层层下沉,层层上浮!架构思路非常清晰!

当北京遇上西雅图(人生大幸),当 Moya 遇上 RxSwift (开发大幸)!

架构设计思维并不是一天就能养成 (Rome was not built in a day) 但是如果你是一个中高级开发,那么架构设计思维你必须拥有。前进道路一点一滴,慢慢积累。上帝也会可怜你.....💪💪💪

就问此时此刻还有谁?45度仰望天空,该死!我这无处安放的魅力!