导入框架
pod 'Moya/RxSwift'
pod 'RxSwift'
pod 'RxCocoa'
pod 'NSObject+Rx'
pod 'Moya-ObjectMapper/RxSwift'
pod 'CryptoSwift'
通用请求 并转模型
import Foundation
import Moya
import CryptoSwift
import RxCocoa
import RxSwift
import NSObject_Rx
import SVProgressHUD
import Moya_ObjectMapper
import ObjectMapper
//初始化360°智能科技请求的provider
let intelligentProvider = MoyaProvider<Intelligent>(requestClosure : timeoutClosure,plugins:[RequestHudPlugin])
let appKey = "加密后的拼接的key"
//定义请求分类
public enum Intelligent {
// MARK: - 轮播图接口
case shufflingFigure(version : Any)
// MARK: - 登录接口
case verifyAccount(loginName : String,passwd : String)
//MARK: - 上传用户头像接口
case replacePicture(photo : Data,version : Any,uid : Int,token : String)
case functionModule(version : Any,uid : Int?,token : String?,id : Int?)
}
//设置请求配置
extension Intelligent : TargetType {
//服务器地址
public var baseURL: URL {
return URL(string:"此处填地址")!
}
//各个请求的具体路径
public var path: String {
switch self {
case .shufflingFigure:
return "/baseconfig/index"
case .verifyAccount:
return "/user/login"
case .replacePicture:
return "/user/userPhotoUpdate"
case .functionModule:
return "/baseconfig/functionLink"
}
}
//请求类型
public var method: Moya.Method {
return .post
}
//这个就是做单元测试模拟的数据,只会在单元测试文件中有作用
public var sampleData: Data {
return "".data(using: String.Encoding.utf8)!
}
//请求任务事件
public var task: Task {
// 请求通用参数(未登录)
var parameterDict = ["osType":"IOS","version":"1.0.0"] as [String : Any];
switch self {
case .shufflingFigure(let version):
parameterDict["appVersion"] = version
break
case .verifyAccount(let loginName, let passwd):
parameterDict["loginName"] = loginName
parameterDict["passwd"] = passwd
parameterDict["appVersion"] = LCZVersion
break
case .replacePicture(let photo,let version, let uid, let token):
let gifData = MultipartFormData(provider: .data(photo), name: "photo", fileName: "file.jpg", mimeType: "image/jpg")
let multipartData = [gifData]
parameterDict["version"] = version
parameterDict["uid"] = uid
parameterDict["token"] = token
return .uploadCompositeMultipart(multipartData, urlParameters: md5Encryption(dict: parameterDict))
case .functionModule(let version, let uid, let token, let id):
parameterDict["version"] = version
parameterDict["uid"] = uid
parameterDict["token"] = token
parameterDict["id"] = id
}
return .requestParameters(parameters: md5Encryption(dict: parameterDict), encoding: URLEncoding.default)
}
//请求头
public var headers: [String : String]? {
return nil
}
//md5加密
func md5Encryption(dict : Dictionary<String,Any>) -> Dictionary<String,Any> {
var parameterDict = dict
//ASCII码排序
let keyAry = parameterDict.keys.sorted()
var urlStr : String! = String()
for key in keyAry {
//拼接字符串
urlStr.append(key + "=" + String(describing: parameterDict[key]!) + "&")
}
//删除最后一个 & 符号
urlStr.removeLast()
//在字符串最后在拼接appKey
urlStr.append(appKey)
//把字符串MD5加密 并转化为大写
let hash : String = urlStr.md5().uppercased()
//添加加密的键值对
parameterDict["sign"] = hash
return parameterDict
}
}
/// 设置接口的超时时间
let timeoutClosure = { (endpoint : Endpoint<Intelligent>,closure : MoyaProvider<Intelligent>.RequestResultClosure) -> Void in
if var urlRequest = try? endpoint.urlRequest() {
urlRequest.timeoutInterval = 10
closure(.success(urlRequest))
}
else{
closure(.failure(MoyaError.requestMapping(endpoint.url)))
}
}
/// 管理网络状态的插件
let RequestHudPlugin = NetworkActivityPlugin { change, target in
switch change {
case .began:
//根据不同的请求,是否显示加载框
switch target as! Intelligent {
case .shufflingFigure(let version):
break
case .verifyAccount(let loginName, let passwd):
LCZHUDTool.show()
break
case .replacePicture(let photo, let version, let uid, let token):
break
case .functionModule(let version, let uid, let token, let id):
break
}
UIApplication.shared.isNetworkActivityIndicatorVisible = true
case .ended:
LCZHUDTool.dismiss()
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}
}
//与数据相关的错误类型
enum DataError: Error {
case abnormalAccount //账户异常需要重新登录
case otherError //其它错误
}
struct defaultModel<T: Mappable>: Mappable {
var code: Int?
var data: T?
var msg: String?
init?(map: Map) {
}
mutating func mapping(map: Map) {
code <- map["code"]
data <- map["data"]
msg <- map["msg"]
}
}
extension MoyaProvider {
public func requestService<T: Mappable>(_ target : Target, _ model: T.Type) -> Single<T> {
return Single<T>.create(subscribe: { (single) -> Disposable in
let request = self.rx.request(target).filterSuccessfulStatusCodes().mapObject(defaultModel<T>.self)
.subscribe(onSuccess: { (result) in
//根据code值 来实现不同的操作
if result.code == 0 { //成功 //返回deta中的模型
single(.success(result.data!))
}else if result.code! == 110 { //让用户去登录
single(.error(DataError.abnormalAccount))
}else { //提示msg
LCZHUDTool.showError(title: result.msg!)
single(.error(DataError.otherError))
}
}) { (error) in
LCZHUDTool.showError(title: "网络请求超时,请检查网络状态!")
}
return Disposables.create([request])
})
}
}
View
import UIKit
class LoginView: BaseView {
/// 手机号输入框
var phoneTextField: UITextField!
/// 密码输入框
var passwordTextField: UITextField!
/// 忘记密码按钮
var forgotPassword: UIButton!
/// 登录按钮
var loginButton: UIButton!
override func config() {
//logo标题
let logoTitleLabel = UILabel()
self.addSubview(logoTitleLabel)
logoTitleLabel.text = "360°智能科技"
logoTitleLabel.font = UIFont.boldFontSize(value: 18)
logoTitleLabel.snp.makeConstraints { (make) in
make.centerX.equalToSuperview().offset(20)
make.top.equalToSuperview().offset(50 + LCZNaviBarHeight + LCZStatusBarHeight)
}
//logo图标
let logoImageView = UIImageView()
self.addSubview(logoImageView)
logoImageView.image = UIImage(named: "login")
logoImageView.snp.makeConstraints { (make) in
make.right.equalTo(logoTitleLabel.snp.left).offset(-10)
make.centerY.equalTo(logoTitleLabel)
}
//手机号输入框
self.phoneTextField = UITextField()
self.addSubview(self.phoneTextField)
self.phoneTextField.placeholder = "请输入手机号"
self.phoneTextField.keyboardType = .numberPad
self.phoneTextField.snp.makeConstraints { (make) in
make.top.equalTo(logoTitleLabel.snp.bottom).offset(40)
make.left.equalToSuperview().offset(40)
make.right.equalToSuperview().offset(-40)
make.height.equalTo(40)
}
//手机号下的分割线
let phoneLine = UIView()
self.addSubview(phoneLine)
phoneLine.backgroundColor = UIColor.RGBColor(245, 245, 245, 1)
phoneLine.snp.makeConstraints { (make) in
make.left.right.bottom.equalTo(self.phoneTextField)
make.height.equalTo(1)
}
//密码输入框
self.passwordTextField = UITextField()
self.addSubview(self.passwordTextField)
self.passwordTextField.placeholder = "请输入密码"
self.passwordTextField.keyboardType = .asciiCapable
self.passwordTextField.isSecureTextEntry = true
self.passwordTextField.snp.makeConstraints { (make) in
make.top.equalTo(phoneLine.snp.bottom).offset(30)
make.left.right.equalTo(phoneLine)
make.height.equalTo(40)
}
//密码分割线
let passwordLine = UIView()
self.addSubview(passwordLine)
passwordLine.backgroundColor = UIColor.RGBColor(245, 245, 245, 1)
passwordLine.snp.makeConstraints { (make) in
make.left.right.bottom.equalTo(self.passwordTextField)
make.height.equalTo(1)
}
//忘记密码按钮
self.forgotPassword = UIButton()
self.addSubview(self.forgotPassword)
self.forgotPassword.setTitle("忘记密码?", for: .normal)
self.forgotPassword.setTitleColor(UIColor.RGBColor(153, 153, 153, 1), for: .normal)
self.forgotPassword.titleLabel?.font = UIFont.fontSize(value: 14)
self.forgotPassword.snp.makeConstraints { (make) in
make.top.equalTo(passwordLine.snp.bottom).offset(20)
make.right.equalTo(passwordLine)
}
//登录按钮
self.loginButton = UIButton()
self.addSubview(self.loginButton)
self.loginButton.setTitle("登录", for: .normal)
self.loginButton.setTitleColor(UIColor.white, for: .normal)
self.loginButton.titleLabel?.font = UIFont.fontSize(value: 18)
self.loginButton.backgroundColor = UIColor.RGBColor(221, 59, 59, 0.5)
self.loginButton.endEditing(true)
self.loginButton.layer.cornerRadius = 5
self.loginButton.clipsToBounds = true
self.loginButton.snp.makeConstraints { (make) in
make.top.equalTo(self.forgotPassword.snp.bottom).offset(30)
make.left.right.equalTo(passwordLine)
make.height.equalTo(40)
}
}
}
ViewModel
import Foundation
import RxSwift
import RxCocoa
import ObjectMapper
struct LoginModel: Mappable {
init?(map: Map) {
}
mutating func mapping(map: Map) {
uid <- map["uid"]
token <- map["token"]
nickName <- map["nickName"]
phone <- map["phone"]
photo <- map["photo"]
}
var uid: Int?
var token: String?
var nickName: String?
var phone: String?
var photo: String?
}
class LoginViewModel {
/// 用户名验证结果
var validatedUserPhone: Observable<Bool>
/// 密码验证结果
var validatedPassword: Observable<Bool>
/// 登录结果
var loginResult: Observable<LoginModel>
/// 初始化
init(input: (
userPhone: Observable<String>,
password: Observable<String>,
loginTaps: Signal<Void>
),
dependency: (
networkService: IntelligentNetworkService,
loginService: LoginService
)) {
//验证手机号
validatedUserPhone = dependency.loginService.validateUserPhone(input.userPhone)
//验证密码
validatedPassword = dependency.loginService.validatePassword(input.password)
//获取最新的用户名和密码
let usernameAndPassword = Observable.combineLatest(input.userPhone, input.password) {
(username: $0, password: $1) }
//获取登录结果
loginResult = input.loginTaps.asObservable().withLatestFrom(usernameAndPassword)
.flatMapLatest{ (arg) -> Single<LoginModel> in
return dependency.networkService.verifyLogin(arg.username, arg.password, LoginModel.self)
}
}
}
Controller
import UIKit
import RxCocoa
import RxSwift
import NSObject_Rx
class LoginViewController: BaseViewController {
var loginView: LoginView!
//视图将要显示
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//设置状态栏颜色
UIApplication.shared.statusBarStyle = .default
}
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = "登录"
//设置X图标
let spacer = UIBarButtonItem(barButtonSystemItem: .stop, target: self,action: #selector(self.BasePopViewController))
spacer.tintColor = UIColor.black
self.navigationItem.leftBarButtonItems = [spacer]
//注册按钮
let rightBarBtn = UIBarButtonItem(title: "注册", style: .plain, target: self, action: nil)
rightBarBtn.tintColor = UIColor.RGBColor(153, 153, 153, 1)
self.navigationItem.rightBarButtonItem = rightBarBtn
//注册响应事件
rightBarBtn.rx.tap.subscribe { _ in
let register = RegisterViewController()
self.BasePushViewController(register)
}.disposed(by: rx.disposeBag)
//主视图
self.loginView = LoginView(frame: self.view.bounds)
self.view.addSubview(self.loginView)
//视图模型
let viewModel = LoginViewModel.init(
input: (self.loginView.phoneTextField.rx.text.orEmpty.asObservable(),
self.loginView.passwordTextField.rx.text.orEmpty.asObservable(),
self.loginView.loginButton.rx.tap.asSignal()
),
dependency:(IntelligentNetworkService(),
LoginService()
)
)
//订阅手机号码和密码的验证结果。
Observable.combineLatest(viewModel.validatedUserPhone,viewModel.validatedPassword) {
$0 && $1
}.subscribe { [weak self] in
self?.loginView.loginButton.backgroundColor = $0.element! ? UIColor.RGBColor(221, 59, 59, 1) : UIColor.RGBColor(221, 59, 59, 0.5)
self?.loginView.loginButton.isEnabled = $0.element!
}.disposed(by: rx.disposeBag)
//登录结果
viewModel.loginResult.subscribe { (result) in
print(result.element)
}.disposed(by: rx.disposeBag)
}
}
登录验证层
import Foundation
import RxSwift
import RxCocoa
import NSObject_Rx
class LoginService {
//账号为11位数
let phoneCount = 11
//密码最少位数
let minPasswordCount = 6
/// 验证用户手机号是否符合要求
///
/// - Parameter userPhone: 手机号码
/// - Returns: true / false
func validateUserPhone(_ userPhone: Observable<String>) -> Observable<Bool> {
return userPhone.map{
$0.count == 11
}
}
/// 验证用户密码是否符合要求
///
/// - Parameter password: 密码
/// - Returns: true / false
func validatePassword(_ password: Observable<String>) -> Observable<Bool> {
return password.map{
$0.count >= 6
}
}
}
网络服务层
import Foundation
import RxCocoa
import RxSwift
import NSObject_Rx
import ObjectMapper
class IntelligentNetworkService {
/// 登录
func verifyLogin(_ phoneNum: String, _ password: String, _ model: LoginModel.Type) -> Single<LoginModel> {
return Single<LoginModel>.create(subscribe: { (single) -> Disposable in
let verifyLogin = intelligentProvider.requestService(.verifyAccount(loginName: phoneNum, passwd: password.md5()), model.self).subscribe(onSuccess: { (model) in
single(.success(model))
}) { (error) in
print(error);
}
return Disposables.create([verifyLogin])
})
}
}