iOS Moya + RxSwift + ObjectMapper + MVVM + CryptoSwift 的使用

76BC10BD-A876-4180-9328-9874091516CF.png

导入框架

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])
        })

    }
}

推荐阅读更多精彩内容