一、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
- 通过调用
SwiftyJSON
把Response
的data
解析成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度仰望天空,该死!我这无处安放的魅力!