概述
上一次写了一些自己入门的一些学习的记录,刚好上周公司有了一个小的售卖机项目需要做一个ios端的,刚好练练手,花了两天时间基本搞的差不多了,写写自己的一些用在项目里的感受,和大家分享一下,希望各位互联网大佬指导一下。
实现
项目的一些操蛋逻辑和需求就不详细介绍了,先看看登录界面的实现
电话号码输入的过程中输入框下面会有随时检测的提醒,正确的时候,点击验证码的按钮状态才是可点击的,当手机和验证码都是符合标准的时候,登录按钮的状态才是可点击的。
- 上代码
fileprivate func obserState() {
// 手机号的观察转换
let phoneState = phoneText.rx.text.orEmpty
.map { $0.validateText(.PhoneNumber) }
.share(replay: 1)
// 和提示绑定 以及获取验证码按钮
phoneState
.bind(to: phoneAlert.ex_AlertState)
.disposed(by: dispose)
phoneState
.bind(to: getCodeBtn.ex_State)
.disposed(by: dispose)
//验证码的转换
let codeState = phoneCode.rx.text.orEmpty
.map { $0.count == 5 }
.share(replay: 1)
//合成手机号和验证码的信号
let comState = Observable.combineLatest(phoneState, codeState) { $0 && $1 }
// 与登录按钮进行绑定
comState.bind(to: applicationBtn.ex_State)
.disposed(by: dispose)
//获取验证码的操作 controlEvent 控制事件
getCodeBtn.rx.tap
.subscribe({ [weak self] _ in self?.loginVM.getLoginPhoneCode(self?.phoneText.text ?? "" , "login") })
.disposed(by: dispose)
// 登录按钮的点击事件
applicationBtn.rx.tap
.subscribe({ [weak self] _ in self?.loginVM.bcLogin(self?.phoneText.text ?? "", self?.phoneCode.text ?? "", {
self?.presenVC()
})
})
.disposed(by: dispose)
// 返回按钮的点击事件
backBtn.rx.tap
.subscribe({ [weak self] _ in self?.dismiss(animated: true, completion: nil) })
.disposed(by: dispose)
}
我在代码的每句注释的都很清楚了,就不一一赘述
这里有一个属性绑定的地方,是需要对你绑定的对象类进行Rx扩展
- 关于按钮的绑定属性
//自定义可绑定的属性
extension UIButton {
var ex_State:AnyObserver<Bool>{
return Binder(self) { button, state in
button.isEnabled = state
button.backgroundColor = state ? BCBtnEnbelColor : UIColor.lightGray
// button.titleLabel?.textColor = state ? UIColor.white : UIColor.orange //这样设置颜色会有延时误差
//这样设置不会有误差
let color = state ? UIColor.orange : UIColor.lightGray
button.setTitleColor(color, for: .normal)
}.asObserver()
}
. 这里代码的注释中我也写到一个问题,就是我用 button.titleLabel?.textColor设置的时候,会与vc中Rx的判定条件产生一点延时误差不能同步响应,具体原因我也不是很清楚,希望有知道的可以指导一下
- 关于提示label的绑定属性
//自定义可绑定属性
extension UILabel {
var ex_AlertState: AnyObserver<Bool> {
return Binder(self){ label , exState in
label.textColor = exState ? UIColor.orange : UIColor.red
label.text = exState ? "输入正确" : "请输入正确手机号"
}.asObserver()
}
以上是Rx的一些简单的绑定和常用的功能实现
Moya部分
关于Moya的详细介绍我这里就不说了,官网也都介绍的很详细了,我这边只是说一下我是怎么在项目里用的
- 上代码
enum BCModuleApi {
case getMobileCode(mobile: String, destination: String) //短信验证码
case goLogin(mobile: String, code: String) //登录
case applyReplenishment(code: String, userId: String, taskNo: Int) //申请补货
case isRobotOpenRequest(actionType: String, identity: Int)//是否正确接收 开机
case doneReplenishment(code: String) //完成补货
case repair(code: String, userId: String, taskNo: Int)//申请维修
case RepairCompleteRequest(code: String ,log: String) //完成维修
// case finishRepairSuccess(identity: String) //App确定售卖机是否已经正确接收完成维修请求
case active(point: String, province: String, city: String, district: String, street: String, room: String, code: String, mobile: String) //设备激活
case activeSuccess(code: String) // App确定售卖机是否已经正确接收激活请求
case unlive(code: String, mobile: String, reason: String)//退服
case regist(name: String, mobile: String, pMobile: String, code: String)//注册
case needAuthen(mobile: String)//用户需要审批的列表
case authenResult(mobile: String, result: String) //审批结果
case synPositionRequest(mobile: String, position: String, type: String) //同步位置
case acceptTask(actionType: String,mobile: String, no: Int)//接受任务 / 拒绝任务
}
extension BCModuleApi: TargetType {
//服务器地址
var baseURL: URL {
return URL.init(string: "#######")!
}
//各个请求的具体路径
var path: String {
return ""
}
//请求类型
var method: Moya.Method {
return .post
}
//请求任务事件(这里附带上参数)
var task: Task {
switch self {
case .getMobileCode(let mobile, let destination):
return .requestParameters(parameters: ["actionType": "getAuthenCode", "mobile": mobile, "destination": destination], encoding: JSONEncoding.default)
case .goLogin(let mobile, let code):
return .requestParameters(parameters: ["actionType": "login", "mobile": mobile, "code": code], encoding: JSONEncoding.default)
case .applyReplenishment(let code, let userId, let taskNo):
return .requestParameters(parameters: ["actionType": "support","code": code, "userId": userId, "taskNo": taskNo], encoding: JSONEncoding.default)
case .doneReplenishment(let code):
return .requestParameters(parameters: ["actionType": "ReplenishmentCompleteRequest", "code": code], encoding: JSONEncoding.default)
case .repair(let code, let userId, let taskNo):
return .requestParameters(parameters: ["actionType": "repair", "code": code, "userId": userId, "task": taskNo], encoding: JSONEncoding.default)
case .active(let point, let province, let city, let district, let street, let room, let code, let mobile):
return .requestParameters(parameters: ["actionType": "active", "point": point, "province": province, "city": city, "district": district, "street": street, "room": room, "code": code, "mobile": mobile], encoding: JSONEncoding.default)
case .unlive(let code, let mobile, let reason):
return .requestParameters(parameters: ["actionType": "unlive", "code": code, "mobile": mobile, "reason": reason], encoding: JSONEncoding.default)
case .regist(let name, let mobile, let pMobile, let code):
return .requestParameters(parameters: ["actionType": "regist", "name": name, "mobile": mobile, "pMobile": pMobile, "code": code], encoding: JSONEncoding.default)
case .needAuthen(let mobile):
return .requestParameters(parameters: ["actionType": "needAuthen", "mobile": mobile], encoding: JSONEncoding.default)
case .authenResult(let mobile, let result):
return .requestParameters(parameters: ["actionType": "authenResult", "mobile": mobile, "result": result], encoding: JSONEncoding.default)
case .synPositionRequest(let mobile, let position , let type):
return .requestParameters(parameters: ["actionType": "synPositionRequest", "mobile": mobile, "position": position, "type": type], encoding: JSONEncoding.default)
case .isRobotOpenRequest(let actionType, let identity):
return .requestParameters(parameters: ["actionType": actionType, "identity": identity], encoding: JSONEncoding.default)
case .acceptTask(let actionType, let mobile, let no):
return .requestParameters(parameters: ["actionType": actionType, "mobile": mobile, "no": no], encoding: JSONEncoding.default)
case .activeSuccess(let code):
return .requestParameters(parameters: ["actionType": "activeSuccess", "code": code], encoding: JSONEncoding.default)
}
}
//各个请求的参数 (方法废弃)
var parameters: [String: Any]? {
return nil
}
//这个就是做单元测试模拟的数据,只会在单元测试文件中有作用
var sampleData: Data {
return "".data(using: String.Encoding.utf8)!
}
//是否执行Alamofire验证
public var validate: Bool {
return false
}
//请求头 以JSONEncoding.default 形式的请求 JSONEncoding类型创建了一个参数对象的JOSN展示,并作为请求体。编码请求的请求头的Content-Type请求字段被设置为application/json; charset=utf-8
var headers: [String : String]? {
let headers: HTTPHeaders = [
"Content-Type": "application/json; charset=utf-8"
]
return headers
}}
- 这里是我的关于Moya的部分数据请求部分
由于项目中的接口不太多,所以我就没有根据业务逻辑进行分类进行,都写在了一起
之前的Moya版本是需要实现的有七个方法,最新版的只需要六个,在代码的注释中我也有标注
//各个请求的参数 (方法废弃)
var parameters: [String: Any]? {
return nil
}
.这个方法被废弃了,现在的参数都是在
var task: Task {
}
方法里统一配置
- 这里提一下
我这边的服务器要求的是把参数全部都以一个JSON的形式传到一个路径下,所以
//各个请求的具体路径
var path: String {
return ""
} 这个方法中我都没配置每个的详细路径
以JSOn的形式传给后台在给参数配置的时候参照Task里的方法
需要注意的事,要记得
var headers: [String : String]? {
let headers: HTTPHeaders = [
"Content-Type": "application/json; charset=utf-8"
]
return headers
}} 在此方法中配置一下请求头,不然。。。
死活获取不到数据,后台那边会显示参数的有问题,乱码。而且奇怪的是,有的接口是会正常的
- 这样我的网络请求层的布置就差不多了接下来是解析的部分
HanyJSON数据解析
//扩展Moya支持HandyJSON的解析
extension ObservableType where E == Response {
public func mapModel<T: HandyJSON>(_ type: T.Type) -> Observable<T> {
return flatMap { response -> Observable<T> in
return Observable.just(response.mapModel(T.self))
}
}
}
extension Response {
func mapModel<T: HandyJSON>(_ type: T.Type) -> T {
let jsonString = String.init(data: data, encoding: .utf8)
if let modelT = JSONDeserializer<T>.deserializeFrom(json: jsonString) {
return modelT
}
return JSONDeserializer<T>.deserializeFrom(json: "{"msg":"请求有误"}")!
}
}
- 这里我是基于Moya的扩展,支持HandyJSON数据解析,让其结合在一起。需要注意的一点是 我这里的代码
return JSONDeserializer<T>.deserializeFrom(json: "{"msg":"请求有误"}")!
这边是防止有时发生不明的错误,对于jsonString的解析失败,如果此时强制解包的时候程序会Crash,所以我这边用了可选绑定,以及这一句写死JSON传,不过这个要根据你的后台返回的基础数据层级格式写,我后台返回的基础层级结构就是如此的。
- 下面回到业务当中,看看他们联合在一起产生的化学反应
业务请求
class LoginVM {
let provider = MoyaProvider<BCModuleApi>()
let dispose = DisposeBag()
func regist(_ name: String, _ mobile: String, _ pMobile: String, _ code: String, _ success:@escaping ()->()) {
provider.rx.request(.regist(name: name, mobile: mobile, pMobile: pMobile, code: code))
.asObservable()
.retry(3)
.mapModel(LoginModel.self)
.subscribe(onNext: { (loginModel) in
guard loginModel.msg == "success" else {
return BCShowMessage.shareMessage.showTipMsg(loginModel.result?.describe ?? "", time: 1)
}
BCShowMessage.shareMessage.showTipMsg("注册成功、请等待短信通知再进行登录", time: 1)
success()
})
.disposed(by: dispose)
}
- 这里是我注册部分的代码
刚刚对于Moya的扩展,到了现在这边都变成了一句代码
.mapModel(LoginModel.self)
是不是很兴奋,一句代码搞定,JSON -> Model
我的model类是
struct LoginModel: HandyJSON {
var msg: String?
var result: ResultModel?
}
struct ResultModel: HandyJSON {
var describe: String?
var userId: String?
var userName: String?
var userMobile: String?
var leaderId: String?
var leaderName: String?
var leaderMobile: String?
var business: String?
}
Model是必须都要遵守HandyJSON协议的,数据的每层Model都是要遵守的。
多界面数据
对于项目中多个界面同时观察同一个数据的变化,一般情况下我们会以通知的情况,数据发生变化的地方就发起通知,其他需要的地方就接收通知。
而在Rx中,这中处理变得非常简单,
只要把你要观察的数据声明成Rx中具有可被观察的属性,并设置单利类,就可以随时在其他地方改变这个数据,并且在你想要观察的地方随时响应。上代码
static let shareHomeVN = HomeVM() //单利 统一操作 首界面和审查列表的数据
let provider = MoyaProvider<BCModuleApi>()
let dispose = DisposeBag()
let modelList = Variable([ListModel]())
func getNeedAuthenList() {
let mobile = UserManager.shareManager.userMobile ?? ""
provider.rx.request(.needAuthen(mobile: mobile))
.retry(3)
.asObservable()
.mapModel(NeedAuthenModel.self)
.subscribe(onNext: { (needModel) in
guard needModel.msg == "success" else {
return BCShowMessage.shareMessage.showTipMsg(needModel.result?.describe ?? "", time: 1)
}
self.modelList.value = needModel.result?.list ?? [ListModel()]
})
.disposed(by: dispose)
}
我在首界面和审查界面共享modelList这条数据,只发起一起请求,可以多个界面随时设置随时响应modelList数据。
//绑定 首界面的绑定操作
homeVM.modelList.asObservable()
.map { $0.count }
.subscribe(onNext: { (numText) in
if numText == 0 {
self.requestNum.isHidden = true
}else {
self.requestNum.text = "\(numText)"
self.requestNum.isHidden = false
}
})
.disposed(by: dispose)
审查界面的数据绑定
needVM.modelList.asObservable()
.subscribe(onNext: { (listModels) in
self.showAlertState(listModels)
})
.disposed(by: dispose)
审查界面的数据处理
cell.cellOKBtn.rx.tap
.subscribe({ [weak self] _ in self?.needVM.authenResult(model.mobile ?? "", "yes", indexPath.row) })
.disposed(by: dispose)
- VM的数据处理
func authenResult(_ mobile: String, _ result: String, _ removeIndex: Int) {
provider.rx.request(.authenResult(mobile: mobile, result: result))
.retry(3)
.asObservable()
.mapModel(NeedAuthenModel.self)
.subscribe(onNext: { (needModel) in
guard needModel.msg == "success" else {
return BCShowMessage.shareMessage.showTipMsg(needModel.result?.describe ?? "", time: 1)
}
self.modelList.value.remove(at: removeIndex)
})
.disposed(by: dispose)
}
- 在每一次的数据操作,共享的界面的绑定都会响应。由于我这边暂时没有数据测试,所以也演示不了。