• 采用绑定机制的MVVM模式会更加强大,所以ViewModel要充分利用RxSwift; 把ViewModel当做黑箱,它可以接收输入,并产生输出,这就是定义ViewModel最好的原则。 方案一 (不采用Subjects) 定义ViewModelType协议 ...
        


    1306450-e481e2c5cbc0b28d.png



    ViewModel需要具备以下特性:

    • 可插拔;
    • 可测试;
    • 采用绑定机制的MVVM模式会更加强大,所以ViewModel要充分利用RxSwift;

    把ViewModel当做黑箱,它可以接收输入,并产生输出,这就是定义ViewModel最好的原则。


    方案一 (不采用Subjects)



    定义ViewModelType协议

    protocol ViewModelType {
      associatedtype Input
      associatedtype Output
    
      func transform(input: Input) -> Output
    }
    

    这种方案简单易行,只需要一次性提供Input给ViewModel,然后ViewModel即可给出Output。



    让我们创建示例Demo:
    输入内容,然后点击Validate按钮。最后,显示校验结果。

    1306450-3875b31692d1bd15.gif



    创建SayHelloViewModel,它需要知道输入的文本以及按钮点击事件,这就是Input。
    然后Output是文本内容。

    final class SayHelloViewModel: ViewModelType {
      struct Input {
        let name: Observable<String>
        let validate: Observable<Void>
      }
    
      struct Output {
        let greeting: Driver<String>
      }
    
      func transform(input: Input) -> Output {
        let greeting = input.validate
          .withLatestFrom(input.name)
          .map { name in
            return "Hello \(name)!"
          }
          .startWith("")
          .asDriver(onErrorJustReturn: ":-(")
    
        return Output(greeting: greeting)
      }
    }
    



    创建SayHelloViewController:

    final class SayHelloViewController: UIViewController {
      
      @IBOutlet weak var nameTextField: UITextField!
      @IBOutlet weak var validateButton: UIButton!
      @IBOutlet weak var greetingLabel: UILabel!
      
      private let viewModel = SayHelloViewModel()
      private let bag = DisposeBag()
      
      override func viewDidLoad() {
        super.viewDidLoad()
        bindViewModel()
      }
      
      private func bindViewModel() {
        let inputs = SayHelloViewModel.Input(name: nameTextField.rx.text.orEmpty.asObservable(),
                                             validate: validateButton.rx.tap.asObservable())
        let outputs = viewModel.transform(input: inputs)
        outputs.greeting
          .drive(greetingLabel.rx.text)
          .disposed(by: bag)
      }
    }
    



    ViewModel应该是可插拔的,那么我们可以把之前定义的ViewModel用于其他View吗?
    现在,如果我们尝试将之前的ViewModel用于带有TableView的View,会发生什么事情?


    /// TableViewCells
    final class TextFieldCell: UITableViewCell {
      @IBOutlet weak var nameTextField: UITextField!
    }
    
    final class ButtonCell: UITableViewCell {
      @IBOutlet weak var validateButton: UIButton!
    }
    
    final class GreetingCell: UITableViewCell {
      @IBOutlet weak var greetingLabel: UILabel!
    }
    
    /// ViewController
    final class SayHelloViewController: UIViewController, UITableViewDataSource {
        static let cellIdentifiers = [
          "TextFieldCell",
          "ButtonCell",
          "GreetingCell"
        ]
    
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
          return TableViewController.cellIdentifiers.count
        }
    
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
          // Classic dequeue work
        }
    
        private let viewModel = SayHelloViewModel()
        private let bag = DisposeBag()
    
        override func viewDidLoad() {
          super.viewDidLoad()
          bindViewModel()
        }
    
        private func bindViewModel() {
          // Let's discuss about this
          let inputs = SayHelloViewModel.Input(name: ??, validate: ??)
        }
    }
    

    然而,我们甚至无法为ViewModel提供Input。
    因为我们不能在创建ViewModel时就获取到UITableView的内容。
    所以,使用这种方案有一个前提条件:在创建ViewModel的Input时,可以获得全部所需的资源。

    这时,你就需要采用第二种方案了!


    第二种方案 (采用Subjects)



    定义ViewModelType协议:

    protocol ViewModelType {
        associatedtype Input
        associatedtype Output
    
        var input: Input { get }
        var output: Output { get }
    }
    

    这样,我们就可以完全自由地选择何时提供输入、何时订阅输出了。
    Subject可以同时充当ObserverObservable,把命令式的编程变为Rx的函数式编程。



    定义采用Subject的ViewModel:

    final class SayHelloViewModel: ViewModelType {
        let input: Input
        let output: Output
    
        struct Input {
            let name: AnyObserver<String>
            let validate: AnyObserver<Void>
        }
    
        struct Output {
            let greeting: Driver<String>
        }
    
        private let nameSubject = ReplaySubject<String>.create(bufferSize: 1)
        private let validateSubject = PublishSubject<Void>()
    
        init() {
            let greeting = validateSubject
                .withLatestFrom(nameSubject)
                .map { name in
                    return "Hello \(name)!"
                }
                .asDriver(onErrorJustReturn: ":-(")
    
            self.output = Output(greeting: greeting)
            self.input = Input(name: nameSubject.asObserver(), validate: validateSubject.asObserver())
        }
    }
    

    这里有几点值得注意的内容:

    • ViewModel的任务还是输入Input产出Output;
    • Subjectsprivate的,所以你只能通过input和output属性与ViewModel交互;
    • 兼具可插拔、可测试的特性,并且充分利用了RxSwift的绑定机制;



    View部分的实现:

    
    /// Every view interacting with a `SayHelloViewModel` instance can conform to this.
    protocol SayHelloViewModelBindable {
        var disposeBag: DisposeBag? { get }
        func bind(to viewModel: SayHelloViewModel)
    }
    
    /// TableViewCells
    final class TextFieldCell: UITableViewCell, SayHelloViewModelBindable {
        @IBOutlet weak var nameTextField: UITextField!
        var disposeBag: DisposeBag?
    
        override func prepareForReuse() {
            super.prepareForReuse()
    
            // Clean Rx subscriptions
            disposeBag = nil
        }
    
        func bind(to viewModel: SayHelloViewModel) {
            let bag = DisposeBag()
            nameTextField.rx
                .text
                .orEmpty
                .bind(to: viewModel.input.name)
                .disposed(by: bag)
            disposeBag = bag
        }
    }
    
    final class ButtonCell: UITableViewCell, SayHelloViewModelBindable {
        @IBOutlet weak var validateButton: UIButton!
        var disposeBag: DisposeBag?
    
        override func prepareForReuse() {
            super.prepareForReuse()
            disposeBag = nil
        }
    
        func bind(to viewModel: SayHelloViewModel) {
            let bag = DisposeBag()
            validateButton.rx
                .tap
                .bind(to: viewModel.input.validate)
                .disposed(by: bag)
            disposeBag = bag
        }
    }
    
    final class GreetingCell: UITableViewCell, SayHelloViewModelBindable {
        @IBOutlet weak var greetingLabel: UILabel!
        var disposeBag: DisposeBag?
    
        override func prepareForReuse() {
            super.prepareForReuse()
            disposeBag = nil
        }
    
        func bind(to viewModel: SayHelloViewModel) {
            let bag = DisposeBag()
            viewModel.output.greeting
                .drive(greetingLabel.rx.text)
                .disposed(by: bag)
            disposeBag = bag
        }
    }
    
    /// View
    class TableViewController: UIViewController, UITableViewDataSource {
        static let cellIdentifiers = [
            "TextFieldCell",
            "ButtonCell",
            "GreetingCell"
        ]
    
        @IBOutlet weak var tableView: UITableView!
    
        private let viewModel = SayHelloViewModel()
        private let bag = DisposeBag()
    
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return TableViewController.cellIdentifiers.count
        }
    
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: TableViewController.cellIdentifiers[indexPath.row])
            (cell as? SayHelloViewModelBindable)?.bind(to: viewModel)
            return cell!
        }
    }
    



    你需要根据自己的需要来决定采用哪一种方案。
    第一种方案简单易行,但是有一定的局限性。
    第二种方案兼容性强,但是定义及使用都略显繁琐。





    参考文章:
    RxSwift + MVVM: how to feed ViewModels



    转载请注明出处,谢谢~

    展开全文
  • 本篇文章是基于RxSwift3.0写的,采用的是Carthage第三方管理工具导入的RxSwift3.0,关于Carthage的安装和使用,请参考Carthage的安装和使用。 最终效果 下载Demo点我 前提准备 首先请大家新建一个s...

    前言

    看了前面的文章,相信很多同学还不知道RxSwift该怎么使用,这篇文件将带领大家一起写一个 注册登录(ps:本例子采用MVVM)的例子进行实战。本篇文章是基于RxSwift3.0写的,采用的是Carthage第三方管理工具导入的RxSwift3.0,关于Carthage的安装和使用,请参考Carthage的安装和使用

    最终效果

    效果图

    下载Demo点我

    前提准备

    首先请大家新建一个swift工程,然后把RxSwift引入到项目中,然后能够编译成功就行。

    然后我们来分析下各个界面的需求:

    注册界面需求:

    • 输入用户名必须大于等于6个字符,不然密码不能输入;
    • 密码必须大于等于6个字符,不然重复密码不能输入;
    • 重复密码和密码必须一样, 不能注册按钮不能点击;
    • 点击注册按钮,提示注册成功或者注册失败;
    • 注册成功会写进本地的plist文件,然后输入用户名会检测该用户名是否已注册

    登录界面需求:

    • 点击输入用户名,检测是否已存在,如果存在,户名可用,否则提示用户名不存在;
    • 输入密码,点击登录,如果密码错则提示密码错误,否则进入列表界面,提示登录成功。

    列表界面需求:

    • 输入联系人的首字母进行筛选

    好了,分析完上面的需求之后,是时候展示真正的技术了,let's go。

    注册界面

    大家现在storyboard中建立出下面这个样子的界面(ps:添加约束不在本篇范围内):
    图1

    创建对应的文件

    然后建立一个对应的控制器RegisterViewController类,另外创建一个RegisterViewModel.swift,将RegisterViewControllerstoryboard中的控制器关联,RegisterViewController看起来应该是这样子的:

    class RegisterViewController: UIViewController {
        @IBOutlet weak var userNameTextField: UITextField!
        @IBOutlet weak var nameLabel: UILabel!
        
        @IBOutlet weak var pwdTextField: UITextField!
        @IBOutlet weak var pwdLabel: UILabel!
        
        @IBOutlet weak var rePwdTextField: UITextField!
        @IBOutlet weak var rePwdLabel: UILabel!
        
        @IBOutlet weak var registButton: UIButton!
        @IBOutlet weak var loginButton: UIBarButtonItem!
            
        override func viewDidLoad() {
            super.viewDidLoad()
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    }

    另外,我们创建一个Service.swift文件。
    Service文件主要负责一些网络请求,和一些数据访问的操作。然后供ViewModel使用,由于本次实战没有使用到网络,所以我们只是模拟从本地plist文件中读取用户数据。

    首先我们在Service文件中创建一个ValidationService类,最好不要继承NSObjectSwift中推荐尽量使用原生类。我们考虑到当文本框内容变化的时候,我们需要把文本框的内容当做参数传递进来进行处理,判断是否符合我们的要求,然后返回处理结果,也就是状态。基于此,我们创建一个Protocol.swift文件,创建一个enum用于表示我们处理结果,所以,我们在Protocol.swift文件中添加如下代码:

    enum Result {
        case ok(message:String)
        case empty
        case failed(message:String)
    }

    username处理

    先写出总结:其实就是两个流的传递过程。
    UI操作 -> ViewModel -> 改变数据
    数据改变 -> ViewModel -> UI刷新

    回到我们ServiceValidationService类中,写一个检测username的方法。它看起来应该是这个样子的:

    class ValidationService {
        
        // 单例类
        static let instance = ValidationService()
        private init(){}
        
        let minCharactersCount = 6
        
        func validationUserName(_ name:String) -> Observable<Result> {
            if name.characters.count == 0 { // 当字符串为空的时候,什么也不做
                return Observable.just(Result.empty)
            }
            
            if name.characters.count < minCharactersCount {
                return Observable.just(Result.failed(message: "用户名长度至少为6位"))
            }
            
            if checkHasUserName(name) {
                return Observable.just(Result.failed(message: "用户名已存在"))
            }
            
            return Observable.just(Result.ok(message: "用户名可用"))
        }
        
        func checkHasUserName(_ userName:String) -> Bool {
            let filePath = NSHomeDirectory() + "/Documents/users.plist"
            guard let userDict = NSDictionary(contentsOfFile: filePath) else {
                return false
            }
            
            let usernameArray = userDict.allKeys as NSArray
            
            return usernameArray.contains(userName)
        }
    }

    接下来该处理我们的RegisterViewModel了,我们声明一个username,指定为Variable类型,为什么是一个Variable类型?因为它既是一个Observer,又是一个Observable,所以我们声明它是一个Variable类型的对象。我们对username处理应该会有一个结果,这个结果应该是由界面监听来改变界面显示,因此我们声明一个usernameUseable表示对username处理的一个结果,因为它是一个Observable,所以我们将它声明为Observable类型的对象,所以RegisterViewModel看起来应该是这样子的:

    class RegisterViewModel {
        let username = Variable<String>("")
        
        let usernameUseable:Observable<Result>
        
        init() {
        }
    }

    然后我们再写RegisterViewController,它看起来应该是这样子的:

    private let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        let viewModel = RegisterViewModel()
            
        userNameTextField.rx.text.orEmpty.bind(to: viewModel.username).disposed(by: disposeBag)
    }
    • 其中userNameTextField.rx.text.orEmptyRxCocoa库中的东西,它把TextFiledtext变成了一个Observable,后面的orEmpty我们可以Command点进去看下,它会把String?过滤nil帮我们变为String类型。
    • bind(to:viewModel.username)的意思是viewModel.username作为一个observer(观察者)观察userNameTextField上的内容变化。
    • 因为我们有监听,就要有监听资源的回收,所以我们创建一个disposeBag来盛放我们这些监听的资源。

    现在,回到我们的RegisterViewModel中,我们添加如下代码:

    init() {
        let service = ValidationService.instance
            
        usernameUseable = username.asObservable().flatMapLatest{ username in
            return service.validationUserName(username).observeOn(MainScheduler.instance).catchErrorJustReturn(.failed(message: "userName检测出错")).shareReplay(1)
        }
    }
    • viewModel中,我们把username当做observable(被观察者),然后对里面的元素进行处理之后发射对应的事件。

    下面我们在RegisterViewController中处理我们的username请求结果。我们在ViewDidLoad中添加下列代码:

    viewModel.usernameUseable.bind(to:
    nameLabel.rx.validationResult).addDisposableTo(disposeBag)
    
    viewModel.usernameUseable.bind(to:
    pwdTextField.rx.inputEnabled).addDisposableTo(disposeBag)
    • ViewModelusername处理结果usernameUseable绑定到nameLabel显示文案上,根据不同的结果显示不同的文案;
    • ViewModelusername处理结果usernameUseable绑定到pwdTextField,根据不同的结果判断是否可以输入。

    关于上面的validationResultinputEnabled是需要我们自己去定制的,这就用到了RxSwift 系列(九) -- 那些难以理解的概念文章中的UIBindingObserver了。

    所以,我们在Protocol.swift文件中添加如下代码:

    extension Result {
        var isValid:Bool {
            switch self {
            case .ok:
                return true
            default:
                return false
            }
        }
    }
    
    
    
    extension Result {
        var textColor:UIColor {
            switch self {
            case .ok:
                return UIColor(red: 138.0 / 255.0, green: 221.0 / 255.0, blue: 109.0 / 255.0, alpha: 1.0)
            case .empty:
                return UIColor.black
            case .failed:
                return UIColor.red
            }
        }
    }
    
    extension Result {
        var description: String {
            switch self {
            case let .ok(message):
                return message
            case .empty:
                return ""
            case let .failed(message):
                return message
            }
        }
    }
    
    extension Reactive where Base: UILabel {
        var validationResult: UIBindingObserver<Base, Result> {
            return UIBindingObserver(UIElement: base) { label, result in
                label.textColor = result.textColor
                label.text = result.description
            }
        }
    }
    
    extension Reactive where Base: UITextField {
        var inputEnabled: UIBindingObserver<Base, Result> {
            return UIBindingObserver(UIElement: base) { textFiled, result in
                textFiled.isEnabled = result.isValid
            }
        }
    }
    • 首先,我们对Result进行了扩展,添加了isValid属性,如果状态是ok,这个属性就为true,否则为false
    • 然后对Result添加了一个textColor属性,如果状态为ok则为绿色,否则使用红色
    • 我们对UILabel进行了UIBingObserver,根据result结果,进行它的texttextColor显示
    • 我们对UITextField进行了UIBingObserver,根据result结果,对它的isEnabled进行设置。

    写到这里,我们暂停一下,运行一下项目看下程序的运行情况,试着去输入username尝试一下效果,是不是很激动??

    password处理

    有了上面username的理解,相信大家对password也就熟门熟路了,因此有些细节就不做描述了。

    我们现在对Service中添加对password的处理:

    func validationPassword(_ password:String) -> Result {
        if password.characters.count == 0 {
            return Result.empty
        }
            
        if password.characters.count < minCharactersCount {
            return .failed(message: "密码长度至少为6位")
        }
            
        return .ok(message: "密码可用")
    }
         
    func validationRePassword(_ password:String, _ rePassword: String) -> Result {
        if rePassword.characters.count == 0 {
            return .empty
        }
            
        if rePassword.characters.count < minCharactersCount {
            return .failed(message: "密码长度至少为6位")
        }
            
        if rePassword == password {
            return .ok(message: "密码可用")
        }
            
        return .failed(message: "两次密码不一样")
    }
    • validationPassword处理我们输入的密码;
    • validationRePassword处理我们输入的重复密码;
    • 上面函数的返回值都是Result类型的值,因为我们外面不需要对这个过程进行监听,所以不必返回一个新的序列。

    RegisterViewModel中添加需要的observable

    let password = Variable<String>("")
    let rePassword = Variable<String>("")
    
    let passwordUseable:Observable<Result>
    let rePasswordUseable:Observable<Result>    

    然后在init()中初始化passwordUseablerePasswordUseable

    passwordUseable = password.asObservable().map { passWord in
        return service.validationPassword(passWord)
    }.shareReplay(1)
            
    rePasswordUseable = Observable.combineLatest(password.asObservable(), rePassword.asObservable()) {
        return service.validationRePassword($0, $1)
    }.shareReplay(1)

    回到RegisterViewController中,添加对应的绑定:

    pwdTextField.rx.text.orEmpty.bind(to: viewModel.password).disposed(by: disposeBag)
    
    rePwdTextField.rx.text.orEmpty.bind(to: viewModel.rePassword).disposed(by: disposeBag)
    
    viewModel.passwordUseable.bind(to: pwdLabel.rx.validationResult).addDisposableTo(disposeBag)
    
    viewModel.passwordUseable.bind(to: rePwdTextField.rx.inputEnabled).addDisposableTo(disposeBag)
            
    viewModel.rePasswordUseable.bind(to: rePwdLabel.rx.validationResult).addDisposableTo(disposeBag)

    ?,先放轻松一下,运行程序看看,输入用户名和密码和重复密码感受一下。

    注册按钮处理

    首先我们在Service里面添加一个注册函数:

    func register(_ username:String, password:String) -> Observable<Result> {
        let userDict = [username: password]
            
        if (userDict as NSDictionary).write(toFile: filePath, atomically: true) {
            return Observable.just(Result.ok(message: "注册成功"))
        }else{
            return Observable.just(Result.failed(message: "注册失败"))
        }
    }

    我是直接把注册信息写入到本地的plist文件,写入成功就返回ok,否则就是
    failed。
    回到RegisterViewModel中添加如下代码:

    let registerTaps = PublishSubject<Void>()
    
    let registerButtonEnabled:Observable<Bool>
    let registerResult:Observable<Result>
    • registerTaps我们使用了PublishSubject,因为不需要有初始元素,其实前面的Variable都可以换成PublishSubject。大伙可以试试;
    • registerButtonEnabled就是注册按钮是否可用的输出,这个其实关系到usernamepassword
    • registerResult就只最后注册结果了.

    我们在init()函数中初始化registerButtonEnabledregisterResult,在init()中添加如下代码:

    registerButtonEnabled = Observable.combineLatest(usernameUseable, passwordUseable, rePasswordUseable) { (username, password, repassword) in
            return username.isValid && password.isValid && repassword.isValid
    }.distinctUntilChanged().shareReplay(1)
            
    let usernameAndPwd = Observable.combineLatest(username.asObservable(), password.asObservable()){
        return ($0, $1)
    }
            
    registerResult = registerTaps.asObservable().withLatestFrom(usernameAndPwd).flatMapLatest { (username, password) in
        return service.register(username, password: password).observeOn(MainScheduler.instance).catchErrorJustReturn(Result.failed(message: "注册失败"))
    }.shareReplay(1)
    • registerButtonEnabled的处理,把usernamepasswordrePassword的处理结果绑定到一起,返回一个总的结果流,这是个Bool值的流。
    • 我们先将usernamepassword组合,得到一个元素是它俩组合的元祖的流。
    • 然后对registerTaps事件进行监听,我们拿到每一个元组进行注册行为,涉及到耗时数据库操作,我们需要对这个过程进行监听,所以我们使用flatMap函数,返回一个新的流。

    回到RegisterViewController中,添加按钮的绑定:

    registButton.rx.tap.bind(to: viewModel.registerTaps).disposed(by: disposeBag)
    
    viewModel.registerButtonEnabled.subscribe(onNext: { [weak self](valid) in
        self?.registButton.isEnabled = valid
        self?.registButton.alpha = valid ? 1 : 0.5
    }).disposed(by: disposeBag)
            
    viewModel.registerResult.subscribe(onNext: { [weak self](result) in
        switch result {
        case let .ok(message):
            self?.showAlert(message:message)
        case .empty:
            self?.showAlert(message:"")
        case let .failed(message):
            self?.showAlert(message:message)
        }
    }).disposed(by: disposeBag)

    弹框方法

    func showAlert(message:String) {
        let action = UIAlertAction(title: "确定", style: .default) { [weak self](_) in
            self?.userNameTextField.text = ""
            self?.pwdTextField.text = ""
            self?.rePwdTextField.text = ""
            
            // 这个方法是基于点击确定让所有元素还原才抽出的,可不搭理。                
            self?.setupRx()
        }
            
        let alertController = UIAlertController(title: nil, message: message, preferredStyle: .alert)
        alertController.addAction(action)
        present(alertController, animated: true, completion: nil)
    }

    注意:上述setupRx()是为了点击确定之后,界面上所有的元素还原才抽出的,具体的可以查看demo

    现在,运行项目,我们已经能够正常的注册账号了。?

    登录界面

    首先我们在storyboard中添加登录界面,如下,当点击登录的时候,就跳转到登录界面。

    图2

    创建一个LoginViewController.swiftLoginViewModel.swift文件,有了上述注册功能的讲解,相信登录功能也很容易了。

    我们在Service.swift中添加如下代码:

    func loginUserNameValid(_ userName:String) -> Observable<Result> {
        if userName.characters.count == 0 {
            return Observable.just(Result.empty)
        }
            
        if checkHasUserName(userName) {
            return Observable.just(Result.ok(message: "用户名可用"))
        }
            
        return Observable.just(Result.failed(message: "用户名不存在"))
    }
        
    // 登录
    func login(_ username:String, password:String) -> Observable<Result> {
            
        guard let userDict = NSDictionary(contentsOfFile: filePath),
            let userPass = userDict.object(forKey: username)
        else {
            return Observable.just(Result.empty)
        }
            
        if (userPass as! String) == password {
            return Observable.just(Result.ok(message: "登录成功"))
        }else{
            return Observable.just(Result.failed(message: "密码错误"))
        }
    }
    • 判断用户名是否可用,如果本地plist文件中有这个用户名,就表示可以使用这个用户名登录,用户名可用;
    • 登录方法,如果用户名和密码都正确的话,就登录成功,否则就密码错误;

    然后LoginViewModel.swift,像这样:

    class LoginViewModel {
        
        let usernameUseable:Driver<Result>
        let loginButtonEnabled:Driver<Bool>
        let loginResult:Driver<Result>
        
        init(input:(username:Driver<String>, password:Driver<String>, loginTaps:Driver<Void>), service:ValidationService) {
            
            usernameUseable = input.username.flatMapLatest { userName in
                return service.loginUserNameValid(userName).asDriver(onErrorJustReturn: .failed(message: "连接server失败"))
            }
            
            let usernameAndPass = Driver.combineLatest(input.username,input.password) {
                return ($0, $1)
            }
            
            loginResult = input.loginTaps.withLatestFrom(usernameAndPass).flatMapLatest{ (username, password)  in
                service.login(username, password: password).asDriver(onErrorJustReturn: .failed(message: "连接server失败"))
            }
            
            loginButtonEnabled = input.password.map {
                $0.characters.count > 0
            }.asDriver()
        }
    }
    • 首先我们声明的对象都是Driver类型的,第一个是username处理结果流,第二个是登录按钮是否可用的流,第三个是登录结果流;
    • 下面的init方法,看着和刚才的注册界面不一样。这种写法我参考了官方文档的写法,让大家知道有这种写法。但是我并不推荐大家使用这种方式,因为如果Controller中的元素很多的话,一个一个传过来是很可怕的。
    • 初始化方法传入的是一个input元组,包括usernameDriver序列,passwordDriver序列,还有登录按钮点击的Driver序列,还有Service对象,需要Controller传递过来,其实Controller不应该拥有Service对象。
    • 初始化方法中,我们对传入的序列进行处理和转换成相对应的序列。大家可以看到都使用了Driver,我们不再需要shareReplay(1)
    • 明白了注册界面的东西,想必这些东西也自然很简单了。

    接下来我们在LoginViewController.swift中写,它看来像这样子的:

    override func viewDidLoad() {
            super.viewDidLoad()
            
            title = "登录"
    
            let viewModel = LoginViewModel(input: (username: usernameTextField.rx.text.orEmpty.asDriver(),
                                                   password: passwordTextField.rx.text.orEmpty.asDriver(),
                                                   loginTaps:loginButton.rx.tap.asDriver()),
                                           service: ValidationService.instance)
            
            viewModel.usernameUseable.drive(nameLabel.rx.validationResult).disposed(by: disposeBag)
            
            viewModel.loginButtonEnabled.drive(onNext: { [weak self] (valid) in
                self?.loginButton.isEnabled = valid
                self?.loginButton.alpha = valid ? 1.0 : 0.5
            }).disposed(by: disposeBag)
            
            viewModel.loginResult.drive(onNext: { [weak self](result) in
                switch result {
                case let .ok(message):
                    self?.performSegue(withIdentifier: "showListSegue", sender: nil)
                    self?.showAlert(message: message)
                case .empty:
                    self?.showAlert(message: "")
                case let .failed(message):
                    self?.showAlert(message: message)
                }
            }).disposed(by: disposeBag)
        }
    • 我们给viewModel传入相应的Driver序列。
    • viewModel中的对象进行相应的监听,如果是Driver序列,我们这里不使用bingTo,而是使用的Driver,用法和bingTo一模一样。
    • Deriver的监听一定发生在主线程,所以很适合我们更新UI的操作。
    • 登录成功会跳转到我们的列表界面。

    列表界面

    由于篇幅原因,列表界面就不做很复杂了,简单地弄了些假数据。既然做到这里了,怎么也得把它做完吧。

    let's go,在storyboard中添加一个控制器,布局如下图:
    图3

    然后建立对应的ListViewController.swiftListViewModel.swift文件,因为需要model类,所以创建了一个Contact.swift类,然后添加了contact.plist资源文件。

    首先编写我们的Contact.swift类,它看来像这样子:

    class Contact:NSObject {
        var name:String
        var phone:String
        
        init(name:String, phone:String) {
            self.name = name
            self.phone = phone
        }
    }

    然后在Service.swift文件中,添加一个SearchService类,它看起来像这样:

    class SearchService {
        static let instance = SearchService();
        private init(){}
        
        // 获取联系人
        func getContacts() -> Observable<[Contact]> {
            let contactPath = Bundle.main.path(forResource: "Contact", ofType: "plist")
            let contactArr = NSArray(contentsOfFile: contactPath!) as! Array<[String:String]>
            
            var contacts = [Contact]()
            for contactDict in contactArr {
                let contact = Contact(name:contactDict["name"]!, phone: contactDict["phone"]!)
                contacts.append(contact)
            }
            
            return Observable.just(contacts).observeOn(MainScheduler.instance)
        }
    }
    • 从本地获取数据,然后转换成Contact模型;
    • 我们返回的是一个元素是Contact数组的Observable流。接下来更新UI的操作要在主线程中。

    然后看看我们的ListViewModel.swift,它看起来像这样:

    class ListViewModel {
        var models:Driver<[Contact]>
        
        init(with searchText:Observable<String>, service:SearchService){
            models = searchText.debug()
                .observeOn(ConcurrentDispatchQueueScheduler(qos: .background))
                .flatMap { text in
                    return service.getContacts(withName: text)
                }.asDriver(onErrorJustReturn:[])
        }
    }
    • 我们的models是一个Driver流,因为更新tableView是UI操作;
    • 然后我们使用service去获取数据的操作应该在后台线程去运行,所以添加了observeOn操作;
    • flatMap返回新的observable流,转换成models对应的Driver流。

    注意:因为这里是根据搜索框的内容去搜索数据,因此在SearchService中需要添加一个函数,它看起来应该是这样子的:

    func getContacts(withName name: String) -> Observable<[Contact]> {
            if name == "" {
                return getContacts()
            }
            
            let contactPath = Bundle.main.path(forResource: "Contact", ofType: "plist")
            let contactArr = NSArray(contentsOfFile: contactPath!) as! Array<[String:String]>
            
            var contacts = [Contact]()
            for contactDict in contactArr {
                if contactDict["name"]!.contains(name) {
                    let contact = Contact(name:contactDict["name"]!, phone: contactDict["phone"]!)
                    contacts.append(contact)
                }
            }
            
            return Observable.just(contacts).observeOn(MainScheduler.instance)
        }

    最后,我们的ListViewController就简单了:

    var searchBarText:Observable<String> {
        return searchBar.rx.text.orEmpty.throttle(0.3, scheduler: MainScheduler.instance)
                .distinctUntilChanged()
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "联系人"
            
        let viewModel = ListViewModel(with: searchBarText, service: SearchService.instance)
            
        viewModel.models.drive(tableView.rx.items(cellIdentifier: "cell", cellType: UITableViewCell.self)){(row, element, cell) in
            cell.textLabel?.text = element.name
            cell.detailTextLabel?.text = element.phone
        }.disposed(by: disposeBag)
    }

    发现木有,这里我们么有使用到DataSource,将数据绑定到tableViewitems元素,这是RxCocoatableView的一个扩展方法。我们可以点进去看看,一共有三个items方法,并且文档都有举例,我们使用的是

    public func items<S : Sequence, Cell : UITableViewCell, O : ObservableType where O.E == S>(cellIdentifier: String, cellType: Cell.Type = default) -> (O) -> (@escaping (Int, S.Iterator.Element, Cell) -> Swift.Void) -> Disposable

    这是一个柯里化的方法,不带section的时候使用这个,它有两个参数,一个是循环利用的cellidentifier,一个cell的类型。后面会返回的是一个闭包,在闭包里对cell进行设置。方法用起来比较简单,就是有点难理解。

    ok,到此为止,这次实战也算结束了。运行你的项目看看吧。

    致谢

    如果发现文章有错误的地方,欢迎指出,谢谢!!

    转载于:https://www.cnblogs.com/yujihaia/p/7425684.html

    展开全文
  • ),而恰巧RxSwift已经出来有一些时间了,语法也基本上稳定,遂只身前来试探试探这RxSwift,接着就做了个小Demo,有兴趣的同学可以瞧一瞧~ 结构. ├── Controller │ └── LXFViewController.swift // 主视

    ReactiveX

    最近刚刚把接手的OC项目搞定,经过深思熟虑后,本人决定下个项目起就使用Swift(学了这么久的Swift还没真正用到实际项目里。。。),而恰巧RxSwift已经出来有一些时间了,语法也基本上稳定,遂只身前来试探试探这RxSwift,接着就做了个小Demo,有兴趣的同学可以瞧一瞧~

    Exhibition

    结构

    .
    ├── Controller
    │   └── LXFViewController.swift     // 主视图控制器
    ├── Extension
    │   └── Response+ObjectMapper.swift // Response分类,Moya请求完进行Json转模型或模型数组
    ├── Model
    │   └── LXFModel.swift              // 模型
    ├── Protocol
    │   └── LXFViewModelType.swift      // 定义了模型协议
    ├── Tool
    │   ├── LXFNetworkTool.swift        // 封装Moya请求
    │   └── LXFProgressHUD.swift        // 封装的HUD
    ├── View
    │   ├── LXFViewCell.swift           // 自定义cell
    │   └── LXFViewCell.xib             // cell的xib文件
    └── ViewModel
        └── LXFViewModel.swift          // 视图模型

    第三方库

    RxSwift         // 想玩RxSwift的必备库
    RxCocoa         // 对 UIKit Foundation 进行 Rx 化
    NSObject+Rx     // 为我们提供 rx_disposeBag 
    Moya/RxSwift    // 为RxSwift专用提供,对Alamofire进行封装的一个网络请求库
    ObjectMapper    // Json转模型之必备良品
    RxDataSources   // 帮助我们优雅的使用tableView的数据源方法
    Then            // 提供快速初始化的语法糖
    Kingfisher      // 图片加载库
    SnapKit         // 视图约束库
    Reusable        // 帮助我们优雅的使用自定义cell和view,不再出现Optional
    MJRefresh       // 上拉加载、下拉刷新的库
    SVProgressHUD   // 简单易用的HUD

    敲黑板

    Moya的使用

    Moya是基于Alamofire的网络请求库,这里我使用了Moya/Swift,它在Moya的基础上添加了对RxSwift的接口支持。接下来我们来说下Moya的使用

    一、创建一个枚举,用来存放请求类型,这里我顺便设置相应的路径,等下统一取出来直接赋值即可

    enum LXFNetworkTool {
        enum LXFNetworkCategory: String {
            case all     = "all"
            case android = "Android"
            case ios     = "iOS"
            case welfare = "福利"
        }
        case data(type: LXFNetworkCategory, size:Int, index:Int)
    }

    二、为这个枚举写一个扩展,并遵循塄 TargetType,这个协议的Moya这个库规定的协议,可以按住Commond键+单击左键进入相应的文件进行查看

    extension LXFNetworkTool: TargetType {
        /// baseURL 统一基本的URL
        var baseURL: URL {
            return URL(string: "http://gank.io/api/data/")!
        }
    
        /// path字段会追加至baseURL后面
        var path: String {
            switch self {
            case .data(let type, let size, let index):
                return "\(type.rawValue)/\(size)/\(index)"
            }
        }
    
        /// HTTP的请求方式
        var method: Moya.Method {
            return .get
        }
    
        /// 请求参数(会在请求时进行编码)
        var parameters: [String: Any]? {
            return nil
        }
    
        /// 参数编码方式(这里使用URL的默认方式)
        var parameterEncoding: ParameterEncoding {
            return URLEncoding.default
        }
    
        /// 这里用于单元测试,不需要的就像我一样随便写写
        var sampleData: Data {
            return "LinXunFeng".data(using: .utf8)!
        }
    
        /// 将要被执行的任务(请求:request 下载:upload 上传:download)
        var task: Task {
            return .request
        }
    
        /// 是否执行Alamofire验证,默认值为false
        var validate: Bool {
            return false
        }
    }

    三、定义一个全局变量用于整个项目的网络请求

    let lxfNetTool = RxMoyaProvider<LXFNetworkTool>()

    至此,我们就可以使用这个全局变量来请求数据了

    RxDataSources

    如果你想用传统的方式也行,不过这就失去了使用RxSwift的意义。好吧,我们接下来说说如何优雅的来实现tableView的数据源。其实RxDataSources官网上已经有很明确的使用说明,不过我还是总结一下整个过程吧。

    概念点
    RxDataSources是以section来做为数据结构来传输,这点很重要,可能很多同学会比较疑惑这句话吧,我在此举个例子,在传统的数据源实现的方法中有一个numberOfSection,我们在很多情况下只需要一个section,所以这个方法可实现,也可以不实现,默认返回的就是1,这给我们带来的一个迷惑点:【tableView是由row来组成的】,不知道在坐的各位中有没有是这么想的呢??有的话那从今天开始就要认清楚这一点,【tableView其实是由section组成的】,所以在使用RxDataSources的过程中,即使你的setion只有一个,那你也得返回一个section的数组出去!!!

    一、自定义Section
    在我们自定义的Model中创建一个Section的结构体,并且创建一个扩展,遵循SectionModelType协议,实现相应的协议方法。约定俗成的写法呢请参考如下方式

    LXFModel.swift
    
    struct LXFSection {
        // items就是rows
        var items: [Item]
        // 你也可以这里加你需要的东西,比如 headerView 的 title
    }
    
    extension LXFSection: SectionModelType {
    
        // 重定义 Item 的类型为 LXFModel
        typealias Item = LXFModel
    
        // 实现协议中的方式
        init(original: LXFSection, items: [LXFSection.Item]) {
            self = original
            self.items = items
        }
    }

    二、在控制器下创建一个数据源属性

    以下代码均在 LXFViewController.swift 文件中

    // 创建一个数据源属性,类型为自定义的Section类型
    let dataSource = RxTableViewSectionedReloadDataSource<LXFSection>()

    使用数据源属性绑定我们的cell

    // 绑定cell
    dataSource.configureCell = { ds, tv, ip, item in
        // 这个地方使用了Reusable这个库,在LXFViewCell中遵守了相应的协议
        // 使其方便转换cell为非可选型的相应的cell类型
        let cell = tv.dequeueReusableCell(for: ip) as LXFViewCell
        cell.picView.kf.setImage(with: URL(string: item.url))
        cell.descLabel.text = "描述: \(item.desc)"
        cell.sourceLabel.text = "来源: \(item.source)"
        return cell
    }

    三、将sections序列绑定给我们的rows

    output.sections.asDriver().drive(tableView.rx.items(dataSource:dataSource)).addDisposableTo(rx_disposeBag)

    大功告成,接下来说说section序列的产生

    ViewModel的规范

    我们知道MVVM思想就是将原本在ViewController的视图显示逻辑、验证逻辑、网络请求等代码存放于ViewModel中,让我们手中的ViewController瘦身。这些逻辑由ViewModel负责,外界不需要关心,外界只需要结果,ViewModel也只需要将结果给到外界,基于此,我们定义了一个协议LXFViewModelType

    一、创建一个LXFViewModelType.swift

    LXFViewModelType.swift
    
    // associatedtype 关键字 用来声明一个类型的占位符作为协议定义的一部分
    protocol LXFViewModelType {
        associatedtype Input
        associatedtype Output
    
        func transform(input: Input) -> Output
    }

    二、viewModel遵守LXFViewModelType协议

    1. 我们可以为XFViewModelType的Input和Output定义别名,以示区分,如:你这个viewModel的用于请求首页模块相关联的,则可以命名为:HomeInput 和 HomeOutput
    2. 我们可以丰富我们的 Input 和 Output 。可以看到我为Output添加了一个序列,类型为我们自定义的LXFSection数组,在Input里面添加了一个请求类型(即要请求什么数据,比如首页的数据)
    3. 我们通过 transform 方法将input携带的数据进行处理,生成了一个Output

    注意: 以下代码为了方便阅读,进行了部分删减

    LXFViewModel.swift
    
    extension LXFViewModel: LXFViewModelType {
       // 存放着解析完成的模型数组
       let models = Variable<[LXFModel]>([])
    
        // 为LXFViewModelType的Input和Output定义别名
        typealias Input = LXFInput
        typealias Output = LXFOutput
    
        // 丰富我们的Input和Output
        struct LXFInput {
            // 网络请求类型
            let category: LXFNetworkTool.LXFNetworkCategory
    
            init(category: LXFNetworkTool.LXFNetworkCategory) {
                self.category = category
            }
        }
    
        struct LXFOutput {
            // tableView的sections数据
            let sections: Driver<[LXFSection]>
    
            init(sections: Driver<[LXFSection]>) {
                self.sections = sections
            }
        }
    
        func transform(input: LXFViewModel.LXFInput) -> LXFViewModel.LXFOutput {
            let sections = models.asObservable().map { (models) -> [LXFSection] in
                // 当models的值被改变时会调用,这是Variable的特性
                return [LXFSection(items: models)] // 返回section数组
            }.asDriver(onErrorJustReturn: [])
    
            let output = LXFOutput(sections: sections)
    
            // 接下来的代码是网络请求,请结合项目查看,不然会不方便阅读和理解
        }
    }

    接着我们在ViewController中初始化我们的input,通过transform得到output,然后将我们output中的sections序列绑定tableView的items

    LXFViewController.swift
    
    // 初始化input
    let vmInput = LXFViewModel.LXFInput(category: .welfare)
    // 通过transform得到output
    let vmOutput = viewModel.transform(input: vmInput)
    
    vmOutput.sections.asDriver().drive(tableView.rx.items(dataSource: dataSource)).addDisposableTo(rx_disposeBag)

    RxSwift中使用MJRefresh

    一、定义一个枚举LXFRefreshStatus,用于标志当前刷新状态

    enum LXFRefreshStatus {
        case none
        case beingHeaderRefresh
        case endHeaderRefresh
        case beingFooterRefresh
        case endFooterRefresh
        case noMoreData
    }

    二、在LXFOutput添加一个refreshStatus序列,类型为LXFRefreshStatus

    // 给外界订阅,告诉外界的tableView当前的刷新状态
    let refreshStatus = Variable<LXFRefreshStatus>(.none)

    我们在进行网络请求并得到结果之后,修改refreshStatus的value为相应的LXFRefreshStatus项

    三、外界订阅output的refreshStatus

    外界订阅output的refreshStatus,并且根据接收到的值进行相应的操作

    vmOutput.refreshStatus.asObservable().subscribe(onNext: {[weak self] status in
        switch status {
        case .beingHeaderRefresh:
            self?.tableView.mj_header.beginRefreshing()
        case .endHeaderRefresh:
            self?.tableView.mj_header.endRefreshing()
        case .beingFooterRefresh:
            self?.tableView.mj_footer.beginRefreshing()
        case .endFooterRefresh:
            self?.tableView.mj_footer.endRefreshing()
        case .noMoreData:
            self?.tableView.mj_footer.endRefreshingWithNoMoreData()
        default:
            break
        }
    }).addDisposableTo(rx_disposeBag)

    四、output提供一个requestCommond用于请求数据

    PublishSubject 的特点:即可以作为Observable,也可以作为Observer,说白了就是可以发送信号,也可以订阅信号

    // 外界通过该属性告诉viewModel加载数据(传入的值是为了标志是否重新加载)
    let requestCommond = PublishSubject<Bool>()

    在transform中,我们对生成的output的requestCommond进行订阅

    output.requestCommond.subscribe(onNext: {[unowned self] isReloadData in
        self.index = isReloadData ? 1 : self.index+1
        lxfNetTool.request(.data(type: input.category, size: 10, index: self.index)).mapArray(LXFModel.self).subscribe({ [weak self] (event) in
            switch event {
            case let .next(modelArr):
                self?.models.value = isReloadData ? modelArr : (self?.models.value ?? []) + modelArr
                LXFProgressHUD.showSuccess("加载成功")
            case let .error(error):
                LXFProgressHUD.showError(error.localizedDescription)
            case .completed:
                output.refreshStatus.value = isReloadData ? .endHeaderRefresh : .endFooterRefresh
            }
        }).addDisposableTo(self.rx_disposeBag)
    }).addDisposableTo(rx_disposeBag)

    五、在ViewController中初始化刷新控件

    为tableView设置刷新控件,并且在创建刷新控件的回调中使用output的requestCommond发射信号

    tableView.mj_header = MJRefreshNormalHeader(refreshingBlock: { 
        vmOutput.requestCommond.onNext(true)
    })
    tableView.mj_footer = MJRefreshAutoNormalFooter(refreshingBlock: { 
        vmOutput.requestCommond.onNext(false)
    })

    总结流程:

    1. ViewController已经拿到output,当下拉加载数据的时候,使用output的requestCommond发射信息,告诉viewModel我们要加载数据

    2. viewModel请求数据,在处理完json转模型或模型数组后修改models,当models的值被修改的时候会发信号给sections,sections在ViewController已经绑定到tableView的items了,所以此时tableView的数据会被更新。接着我们根据请求结果,修改output的refreshStatus属性的值

    3. 当output的refreshStatus属性的值改变后,会发射信号,由于外界之前已经订阅了output的refreshStatus,此时就会根据refreshStatus的新值来处理刷新控件的状态

    好了,附上RxSwiftDemo。完结撒花

    展开全文
  • RxSwift学习之六(调度者)

    Rxswift 常用的数据处理

    Target Action

    实例1

    • 传统代码
    button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
    
    func buttonTapped() {
        print("button Tapped")
    }
    
    • Rxswift代码
    button.rx.tap
        .subscribe(onNext: {
            print("button Tapped")
        })
        .disposed(by: disposeBag)
    

    你不需要使用 Target Action,这样使得代码逻辑清晰可见。

    代理

    实例2

    • 传统代码
    class ViewController: UIViewController {
        ...
        override func viewDidLoad() {
            super.viewDidLoad()
            scrollView.delegate = self
        }
    }
    
    extension ViewController: UIScrollViewDelegate {
        func scrollViewDidScroll(_ scrollView: UIScrollView) {
            print("contentOffset: \(scrollView.contentOffset)")
        }
    }
    
    • Rxswift代码
    class ViewController: UIViewController {
        ...
        override func viewDidLoad() {
            super.viewDidLoad()
    
            scrollView.rx.contentOffset
                .subscribe(onNext: { contentOffset in
                    print("contentOffset: \(contentOffset)")
                })
                .disposed(by: disposeBag)
        }
    }
    

    Rxswift实现的代理,你不需要书写代理的配置代码,就能获得想要的结果。

    通知

    实例3

    • 传统代码
    var ntfObserver: NSObjectProtocol!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        ntfObserver = NotificationCenter.default.addObserver(
              forName: .UIApplicationWillEnterForeground,
              object: nil, queue: nil) { (notification) in
            print("Application Will Enter Foreground")
        }
    }
    
    deinit {
        NotificationCenter.default.removeObserver(ntfObserver)
    }
    
    • Rxswift代码
    NotificationCenter.default.rx.notification(UIResponder.keyboardWillShowNotification)
                .subscribe(onNext: { (noti) in
                    print(noti)
                })
            .disposed(by: disposeBag)
    

    闭包回调

    实例4

    • 传统代码
    URLSession.shared.dataTask(with: URLRequest(url: url)) {
        (data, response, error) in
        guard error == nil else {
            print("Data Task Error: \(error!)")
            return
        }
    
        guard let data = data else {
            print("Data Task Error: unknown")
            return
        }
    
        print("Data Task Success with count: \(data.count)")
    }.resume()
    
    • Rxswift代码
    URLSession.shared.rx.data(request: URLRequest(url: url))
        .subscribe(onNext: { data in
            print("Data Task Success with count: \(data.count)")
        }, onError: { error in
            print("Data Task Error: \(error)")
        })
        .disposed(by: disposeBag)
    

    KVO

    实例5

    • 传统代码
    
    
    • Rxswift代码
    //监听person对象的name的变化
     self.person.rx.observeWeakly(String.self, "name")
                .subscribe(onNext: { (value) in
                    print(value as Any)
                })
                .disposed(by: disposeBag)
    

    手势

    实例6

    • 传统代码
    
    
    • Rxswift代码
     let disposeBag = DisposeBag()
     let tap = UITapGestureRecognizer()
            self.label.addGestureRecognizer(tap)
            self.label.isUserInteractionEnabled = true
            tap.rx.event.subscribe(onNext: { (tap) in
                print(tap.view)
            })
            .disposed(by: disposeBag)
    

    网路请求

    实例7

    • 传统代码
    
    
    • Rxswift代码
     let url = URL(string: "https://www.baidu.com")
     URLSession.shared.rx.response(request: URLRequest(url:  url!)).subscribe(onNext: { (response,data) in
                print(response)
            }, onError: { (error) in
                print(error)
            }, onCompleted: {
                
            }).disposed(by: disposeBag)
    

    定时器

    实例8

    • 传统代码
    
    
    • Rxswift代码
    let disposeBag = DisposeBag()
    var timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
            timer.subscribe(onNext: { (num) in
                print(num)
            })
            .disposed(by: disposeBag)
    

    多个任务之间有依赖关系

    实例9
    例如,先通过用户名密码取得 Token 然后通过 Token 取得用户信息

    • 传统代码
    /// 用回调的方式封装接口
    enum API {
    
        /// 通过用户名密码取得一个 token
        static func token(username: String, password: String,
            success: (String) -> Void,
            failure: (Error) -> Void) { ... }
    
        /// 通过 token 取得用户信息
        static func userinfo(token: String,
            success: (UserInfo) -> Void,
            failure: (Error) -> Void) { ... }
    }
    
    /// 通过用户名和密码获取用户信息
    API.token(username: "beeth0ven", password: "987654321",
        success: { token in
            API.userInfo(token: token,
                success: { userInfo in
                    print("获取用户信息成功: \(userInfo)")
                },
                failure: { error in
                    print("获取用户信息失败: \(error)")
            })
        },
        failure: { error in
            print("获取用户信息失败: \(error)")
    })
    
    • Rxswift代码
    /// 用 Rx 封装接口
    enum API {
    
        /// 通过用户名密码取得一个 token
        static func token(username: String, password: String) -> Observable<String> { ... }
    
        /// 通过 token 取得用户信息
        static func userInfo(token: String) -> Observable<UserInfo> { ... }
    }
    
    /// 通过用户名和密码获取用户信息
    API.token(username: "beeth0ven", password: "987654321")
        .flatMapLatest(API.userInfo)
        .subscribe(onNext: { userInfo in
            print("获取用户信息成功: \(userInfo)")
        }, onError: { error in
            print("获取用户信息失败: \(error)")
        })
        .disposed(by: disposeBag)
    

    等待多个并发任务完成后处理结果

    实例10
    例如,需要将两个网络请求合并成一个

    通过 Rx 来实现:

    /// 用 Rx 封装接口
    enum API {
    
        /// 取得老师的详细信息
        static func teacher(teacherId: Int) -> Observable<Teacher> { ... }
    
        /// 取得老师的评论
        static func teacherComments(teacherId: Int) -> Observable<[Comment]> { ... }
    }
    
    /// 同时取得老师信息和老师评论
    Observable.zip(
          API.teacher(teacherId: teacherId),
          API.teacherComments(teacherId: teacherId)
        ).subscribe(onNext: { (teacher, comments) in
            print("获取老师信息成功: \(teacher)")
            print("获取老师评论成功: \(comments.count) 条")
        }, onError: { error in
            print("获取老师信息或评论失败: \(error)")
        })
        .disposed(by: disposeBag)
    

    这样你可用寥寥几行代码来完成相当复杂的异步操作。

    数据绑定

    实例11

    在 RxSwift 里有一个比较重要的概念就是数据绑定(订阅)。就是指将可监听序列绑定到观察者上:

    我们对比一下这两段代码:

    1. 传统代码:

    将一个单独的图片设置到imageView上

    let image: UIImage = UIImage(named: ...)
    imageView.image = image
    
    1. Rx代码:
    let image: Observable<UIImage> = ...
    image.bind(to: imageView.rx.image)
    

    Rx代码:上面这段代码是将一个图片序列 “同步” 到imageView上。这个序列里面的图片可以是异步产生的。这里定义的 image 就是上图中蓝色部分(可监听序列),imageView.rx.image就是上图中橙色部分(观察者)。而这种 “同步机制” 就是数据绑定(订阅)。

    Rxswift UI 用法

    UILabel

    实例30

    • 传统代码
    
    
    • Rxswift代码
    1. Rxswift简单使用UILabel
    import UIKit
    import RxSwift
    import RxCocoa
     
    class ViewController: UIViewController {
         
        let disposeBag = DisposeBag()
         
        override func viewDidLoad() {
             
            //创建文本标签
            let label = UILabel(frame:CGRect(x:20, y:40, width:300, height:100))
            self.view.addSubview(label)
             
            //创建一个计时器(每0.1秒发送一个索引数)
            let timer = Observable<Int>.interval(0.1, scheduler: MainScheduler.instance)
             
            //将已过去的时间格式化成想要的字符串,并绑定到label上
            timer.map{ String(format: "%0.2d:%0.2d.%0.1d",
                              arguments: [($0 / 600) % 600, ($0 % 600 ) / 10, $0 % 10]) }
            .bind(to: label.rx.text)
            .disposed(by: disposeBag)
        }
    }
    
    1. UILabel富文本 :将数据绑定到 attributedText 属性上
    import UIKit
    import RxSwift
    import RxCocoa
     
    class ViewController: UIViewController {
         
        let disposeBag = DisposeBag()
         
        override func viewDidLoad() {
             
            //创建文本标签
            let label = UILabel(frame:CGRect(x:20, y:40, width:300, height:100))
            self.view.addSubview(label)
             
            //创建一个计时器(每0.1秒发送一个索引数)
            let timer = Observable<Int>.interval(0.1, scheduler: MainScheduler.instance)
             
            //将已过去的时间格式化成想要的字符串,并绑定到label上
            timer.map(formatTimeInterval)
            .bind(to: label.rx.attributedText)
            .disposed(by: disposeBag)
        }
         
        //将数字转成对应的富文本
        func formatTimeInterval(ms: NSInteger) -> NSMutableAttributedString {
            let string = String(format: "%0.2d:%0.2d.%0.1d",
                             arguments: [(ms / 600) % 600, (ms % 600 ) / 10, ms % 10])
            //富文本设置
            let attributeString = NSMutableAttributedString(string: string)
            //从文本0开始6个字符字体HelveticaNeue-Bold,16号
            attributeString.addAttribute(NSAttributedStringKey.font,
                                         value: UIFont(name: "HelveticaNeue-Bold", size: 16)!,
                                         range: NSMakeRange(0, 5))
            //设置字体颜色
            attributeString.addAttribute(NSAttributedStringKey.foregroundColor,
                                         value: UIColor.white, range: NSMakeRange(0, 5))
            //设置文字背景颜色
            attributeString.addAttribute(NSAttributedStringKey.backgroundColor,
                                         value: UIColor.orange, range: NSMakeRange(0, 5))
            return attributeString
        }
    }
    

    UIButton

    实例40

    • 传统代码
     self.button.addTarget(self, action:#selector(buttonTapped(sender:)), for: UIControlEvents.touchUpInside)
        @objc func buttonTapped(sender:UIButton?){
            
        }
    
    • Rxswift代码
     let disposeBag = DisposeBag()
     //由于tap事件里点击事件用的最多,所以RX默认的tap就是点击事件
     self.button.rx.tap
                .subscribe(onNext: { () in
                    print("点击来了")
                })
                .disposed(by: disposeBag)
    
    
    
       //RXSwift监听按钮除了点击外的事件:
        self.button.rx.controlEvent(.touchUpOutside).subscribe(onNext: { () in
    
            })
                .disposed(by: disposeBag)
    
    import UIKit
    import RxSwift
    import RxCocoa
     
    class ViewController: UIViewController {
         
        let disposeBag = DisposeBag()
         
        @IBOutlet weak var button: UIButton!
         
        override func viewDidLoad() {
            //按钮点击响应1
            button.rx.tap
                .subscribe(onNext: { [weak self] in
                    self?.showMessage("按钮被点击")
                })
                .disposed(by: disposeBag)
    
            //按钮点击响应2
            button.rx.tap
                 .bind { [weak self] in
                 self?.showMessage("按钮被点击")
                   }
                   .disposed(by: disposeBag)
    
    //
        }
    
    //按钮标题(title)的绑定
    func test1() {
    //创建一个计时器(每1秒发送一个索引数)
    let timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
     
    //根据索引数拼接最新的标题,并绑定到button上
    timer.map{"计数\($0)"}
        .bind(to: button.rx.title(for: .normal))
        .disposed(by: disposeBag)
    }
    
    //按钮富文本标题(attributedTitle)的绑定
    func test2() {
     //创建一个计时器(每1秒发送一个索引数)
            let timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
             
            //将已过去的时间格式化成想要的字符串,并绑定到button上
            timer.map(formatTimeInterval)
                .bind(to: button.rx.attributedTitle())
                .disposed(by: disposeBag)
    }
    
    //按钮图标(image)的绑定
    func test3() {
    //创建一个计时器(每1秒发送一个索引数)
    let timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
     
    //根据索引数选择对应的按钮图标,并绑定到button上
    timer.map({
        let name = $0%2 == 0 ? "back" : "forward"
        return UIImage(named: name)!
    })
    .bind(to: button.rx.image())
    .disposed(by: disposeBag)
    }
    
    //按钮背景图片(backgroundImage)的绑定
    func test4() {
    //创建一个计时器(每1秒发送一个索引数)
    let timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
     
    //根据索引数选择对应的按钮背景图,并绑定到button上
    timer.map{ UIImage(named: "\($0%2)")! }
        .bind(to: button.rx.backgroundImage())
        .disposed(by: disposeBag)
    }
    
    
    
    //将数字转成对应的富文本
        func formatTimeInterval(ms: NSInteger) -> NSMutableAttributedString {
            let string = String(format: "%0.2d:%0.2d.%0.1d",
                                arguments: [(ms / 600) % 600, (ms % 600 ) / 10, ms % 10])
            //富文本设置
            let attributeString = NSMutableAttributedString(string: string)
            //从文本0开始6个字符字体HelveticaNeue-Bold,16号
            attributeString.addAttribute(NSAttributedStringKey.font,
                                         value: UIFont(name: "HelveticaNeue-Bold", size: 16)!,
                                         range: NSMakeRange(0, 5))
            //设置字体颜色
            attributeString.addAttribute(NSAttributedStringKey.foregroundColor,
                                         value: UIColor.white, range: NSMakeRange(0, 5))
            //设置文字背景颜色
            attributeString.addAttribute(NSAttributedStringKey.backgroundColor,
                                         value: UIColor.orange, range: NSMakeRange(0, 5))
            return attributeString
        }
         
        //显示消息提示框
        func showMessage(_ text: String) {
            let alertController = UIAlertController(title: text, message: nil, preferredStyle: .alert)
            let cancelAction = UIAlertAction(title: "确定", style: .cancel, handler: nil)
            alertController.addAction(cancelAction)
            self.present(alertController, animated: true, completion: nil)
        }
    }
    

    UIBarButtonItem

    实例50

    • 传统代码
    
    
    • Rxswift代码
    
    

    UISwitch

    实例60

    • 传统代码
    
    
    • Rxswift代码
    import UIKit
    import RxSwift
    import RxCocoa
     
    class ViewController: UIViewController {
         
        //分段选择控件
        @IBOutlet weak var segmented: UISegmentedControl!
        //图片显示控件
        @IBOutlet weak var imageView: UIImageView!
         
        let disposeBag = DisposeBag()
         
        override func viewDidLoad() {
            //创建一个当前需要显示的图片的可观察序列
            let showImageObservable: Observable<UIImage> =
                segmented.rx.selectedSegmentIndex.asObservable().map {
                    let images = ["js.png", "php.png", "react.png"]
                    return UIImage(named: images[$0])!
            }
             
            //把需要显示的图片绑定到 imageView 上
            showImageObservable.bind(to: imageView.rx.image)
                .disposed(by: disposeBag)
        }
    
    func test1() {
    switch1.rx.isOn.asObservable()
        .subscribe(onNext: {
            print("当前开关状态:\($0)")
        })
        .disposed(by: disposeBag)
    }
    
    func test2() {
    switch1.rx.isOn
        .bind(to: button1.rx.isEnabled)
        .disposed(by: disposeBag)
    }
    }
    

    UISegmentedControl

    实例70

    • 传统代码
    
    
    • Rxswift代码
    import UIKit
    import RxSwift
    import RxCocoa
     
    class ViewController: UIViewController {
         
        //分段选择控件
        @IBOutlet weak var segmented: UISegmentedControl!
        //图片显示控件
        @IBOutlet weak var imageView: UIImageView!
         
        let disposeBag = DisposeBag()
         
        override func viewDidLoad() {
            //创建一个当前需要显示的图片的可观察序列
            let showImageObservable: Observable<UIImage> =
                segmented.rx.selectedSegmentIndex.asObservable().map {
                    let images = ["js.png", "php.png", "react.png"]
                    return UIImage(named: images[$0])!
            }
             
            //把需要显示的图片绑定到 imageView 上
            showImageObservable.bind(to: imageView.rx.image)
                .disposed(by: disposeBag)
        }
    
    func test1() {
    segmented.rx.selectedSegmentIndex.asObservable()
        .subscribe(onNext: {
            print("当前项:\($0)")
        })
        .disposed(by: disposeBag)
    }
    }
    
    

    UIActivityIndicatorView

    实例80

    • 传统代码
    
    
    • Rxswift代码
    
    

    UITextField

    实例90:UITextField使用Rxswift的基本用法

    • 传统代码
    
    
    • Rxswift代码
    self.textFiled.rx.text.orEmpty
                .subscribe(onNext: { (text) in
                   print(text)
                })
                .disposed(by: disposeBag)
    // textfiled绑定Button的文字
    self.textFiled.rx.text
                .bind(to: self.button.rx.title())
                .disposed(by: disposeBag)
    

    实例91:Rxswift监听单个 textField 内容的变化

    • Rxswift代码
    import UIKit
    import RxSwift
    import RxCocoa
     
    class ViewController: UIViewController {
         
        let disposeBag = DisposeBag()
         
        override func viewDidLoad() {
             
            //创建文本输入框
            let textField = UITextField(frame: CGRect(x:10, y:80, width:200, height:30))
            textField.borderStyle = UITextBorderStyle.roundedRect
            self.view.addSubview(textField)
             
            //当文本框内容改变时,将内容输出到控制台上
            textField.rx.text.orEmpty.asObservable()
                .subscribe(onNext: {
                    print("您输入的是:\($0)")
                })
                .disposed(by: disposeBag)
    
    //当文本框内容改变时,将内容输出到控制台上
    textField.rx.text.orEmpty.changed
        .subscribe(onNext: {
            print("您输入的是:\($0)")
        })
        .disposed(by: disposeBag)
        
        }
    }
    

    实例92:Rxswift将textField的内容绑定到其他控件上

    • Rxswift代码
    import UIKit
    import RxSwift
    import RxCocoa
     
    class ViewController: UIViewController {
         
        let disposeBag = DisposeBag()
         
        override func viewDidLoad() {
             
            //创建文本输入框
            let inputField = UITextField(frame: CGRect(x:10, y:80, width:200, height:30))
            inputField.borderStyle = UITextBorderStyle.roundedRect
            self.view.addSubview(inputField)
             
            //创建文本输出框
            let outputField = UITextField(frame: CGRect(x:10, y:150, width:200, height:30))
            outputField.borderStyle = UITextBorderStyle.roundedRect
            self.view.addSubview(outputField)
             
            //创建文本标签
            let label = UILabel(frame:CGRect(x:20, y:190, width:300, height:30))
            self.view.addSubview(label)
             
            //创建按钮
            let button:UIButton = UIButton(type:.system)
            button.frame = CGRect(x:20, y:230, width:40, height:30)
            button.setTitle("提交", for:.normal)
            self.view.addSubview(button)
             
             
            //当文本框内容改变
            let input = inputField.rx.text.orEmpty.asDriver() // 将普通序列转换为 Driver
                .throttle(0.3) //在主线程中操作,0.3秒内值若多次改变,取最后一次
             
            //内容绑定到另一个输入框中
            input.drive(outputField.rx.text)
                .disposed(by: disposeBag)
             
            //内容绑定到文本标签中
            input.map{ "当前字数:\($0.count)" }
                .drive(label.rx.text)
                .disposed(by: disposeBag)
             
            //根据内容字数决定按钮是否可用
            input.map{ $0.count > 5 }
                .drive(button.rx.isEnabled)
                .disposed(by: disposeBag)
        }
    }
    

    实例93:Rxswift同时监听多个 textField 内容的变化

    • Rxswift代码
    import UIKit
    import RxSwift
    import RxCocoa
     
    class ViewController: UIViewController {
         
        let disposeBag = DisposeBag()
         
        @IBOutlet weak var textField1: UITextField!
        @IBOutlet weak var textField2: UITextField!
        @IBOutlet weak var label: UILabel!
         
        override func viewDidLoad() {
             
            Observable.combineLatest(textField1.rx.text.orEmpty, textField2.rx.text.orEmpty) {
                textValue1, textValue2 -> String in
                return "你输入的号码是:\(textValue1)-\(textValue2)"
                }
                .map { $0 }
                .bind(to: label.rx.text)
                .disposed(by: disposeBag)
        }
    }
    

    实例94:Rxswift实现textField事件监听
    通过 rx.controlEvent 可以监听输入框的各种事件,且多个事件状态可以自由组合。除了各种 UI 控件都有的 touch 事件外,输入框还有如下几个独有的事件:

    • editingDidBegin:开始编辑(开始输入内容)

    • editingChanged:输入内容发生改变

    • editingDidEnd:结束编辑

    • editingDidEndOnExit:按下 return 键结束编辑

    • allEditingEvents:包含前面的所有编辑相关事件

    • Rxswift代码

    textField.rx.controlEvent([.editingDidBegin]) //状态可以组合
        .asObservable()
        .subscribe(onNext: { _ in
            print("开始编辑内容!")
        }).disposed(by: disposeBag)
    
    import UIKit
    import RxSwift
    import RxCocoa
     
    class ViewController: UIViewController {
     
        //用户名输入框
        @IBOutlet weak var username: UITextField!
         
        //密码输入框
        @IBOutlet weak var password: UITextField!
         
        let disposeBag = DisposeBag()
         
        override func viewDidLoad() {
            super.viewDidLoad()
             
            //在用户名输入框中按下 return 键
            username.rx.controlEvent(.editingDidEndOnExit).subscribe(onNext: {
                [weak self] (_) in
                self?.password.becomeFirstResponder()
            }).disposed(by: disposeBag)
             
             //在密码输入框中按下 return 键
            password.rx.controlEvent(.editingDidEndOnExit).subscribe(onNext: {
                [weak self] (_) in
                self?.password.resignFirstResponder()
            }).disposed(by: disposeBag)
        }
    }
    

    实例95:Rxswift实现textField事件监听

    • Rxswift代码
    
    

    UITextView

    实例100

    • 传统代码
    
    
    • Rxswift代码
    import UIKit
    import RxSwift
    import RxCocoa
     
    class ViewController: UIViewController {
         
        let disposeBag = DisposeBag()
         
        @IBOutlet weak var textView: UITextView!
         
        override func viewDidLoad() {
             
            //开始编辑响应
            textView.rx.didBeginEditing
                .subscribe(onNext: {
                    print("开始编辑")
                })
                .disposed(by: disposeBag)
             
            //结束编辑响应
            textView.rx.didEndEditing
                .subscribe(onNext: {
                    print("结束编辑")
                })
                .disposed(by: disposeBag)
             
            //内容发生变化响应
            textView.rx.didChange
                .subscribe(onNext: {
                    print("内容发生改变")
                })
                .disposed(by: disposeBag)
             
            //选中部分变化响应
            textView.rx.didChangeSelection
                .subscribe(onNext: {
                    print("选中部分发生变化")
                })
                .disposed(by: disposeBag)
        }
    }
    

    UITableView

    • 传统Swift使用UITableView
      实例110
    //歌曲结构体
    struct Music {
        let name: String //歌名
        let singer: String //演唱者
         
        init(name: String, singer: String) {
            self.name = name
            self.singer = singer
        }
    }
    
    //歌曲列表数据源
    struct MusicListViewModel {
        let data = [
            Music(name: "无条件", singer: "陈奕迅"),
            Music(name: "你曾是少年", singer: "S.H.E"),
            Music(name: "从前的我", singer: "陈洁仪"),
            Music(name: "在木星", singer: "朴树"),
        ]
    }
    
    
    class ViewController: UIViewController {
     
        //tableView对象
        @IBOutlet weak var tableView: UITableView!
         
        //歌曲列表数据源
        let musicListViewModel = MusicListViewModel()
         
        override func viewDidLoad() {
            super.viewDidLoad()
             
            //设置代理
            tableView.dataSource = self
            tableView.delegate = self
        }
    }
     
    extension ViewController: UITableViewDataSource {
        //返回单元格数量
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return musicListViewModel.data.count
        }
         
        //返回对应的单元格
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
            -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "musicCell")!
            let music = musicListViewModel.data[indexPath.row]
            cell.textLabel?.text = music.name
            cell.detailTextLabel?.text = music.singer
            return cell
        }
    }
     
    extension ViewController: UITableViewDelegate {
        //单元格点击
        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
            print("你选中的歌曲信息【\(musicListViewModel.data[indexPath.row])】")
        }
    }
    
    • Rxswift 的UITableView

    实例111

    /*这里我们将 data 属性变成一个可观察序列对象(Observable Squence),
    而对象当中的内容和我们之前在数组当中所包含的内容是完全一样的。
    */
    //歌曲列表数据源
    struct MusicListViewModel {
        let data = Observable.just([
            Music(name: "无条件", singer: "陈奕迅"),
            Music(name: "你曾是少年", singer: "S.H.E"),
            Music(name: "从前的我", singer: "陈洁仪"),
            Music(name: "在木星", singer: "朴树"),
        ])
    }
    
    import UIKit
    import RxSwift
    import RxCocoa
     
    class ViewController: UIViewController {
     
        //tableView对象
        @IBOutlet weak var tableView: UITableView!
         
        //歌曲列表数据源
        let musicListViewModel = MusicListViewModel()
         
        //负责对象销毁
        let disposeBag = DisposeBag()
         
        override func viewDidLoad() {
            super.viewDidLoad()
             
            //将数据源数据绑定到tableView上
            musicListViewModel.data
                .bind(to: 
                /*
           rx.items(cellIdentifier:):这是 Rx 基于 cellForRowAt 数据源方法的一个封装。
           传统方式中我们还要有个 numberOfRowsInSection 方法,
           使用 Rx 后就不再需要了(Rx 已经帮我们完成了相关工作)。
           */ 
           tableView.rx.items(cellIdentifier:"musicCell")) { _, music, cell in
                    cell.textLabel?.text = music.name
                    cell.detailTextLabel?.text = music.singer
                }.disposed(by: disposeBag)
             
            //tableView点击响应
           /*
           rx.modelSelected: 这是 Rx 基于 UITableView委托回调方法 didSelectRowAt 的一个封装。
           */ tableView.rx.modelSelected(Music.self).subscribe(onNext: { music in
                print("你选中的歌曲信息【\(music)】")
            }).disposed(by: disposeBag)
            /*DisposeBag:作用是 Rx 在视图控制器或者其持有者将要销毁的时候,
            自动释法掉绑定在它上面的资源。
            它是通过类似“订阅处置机制”方式实现(类似于 NotificationCenter 的 removeObserver)。*/
        }
    }
    
    

    UICollectionView

    实例120

    • 传统代码
    
    
    • Rxswift代码
    
    

    UIPickerView

    实例130

    • 传统代码
    
    
    • Rxswift代码
    
    

    UIDatePicker

    实例140

    • 传统代码
    
    
    • Rxswift代码
    
    

    UISlider

    实例150

    • 传统代码
    
    
    • Rxswift代码
    
    

    UIStepper

    实例160

    • 传统代码
    
    
    • Rxswift代码
    
    
    展开全文
  • 广告:从前段时间开始接触RxSwift,跟着网上教程一点点的敲一些基础代码,慢慢地接触到越来越深的知识点,记录在此&gt;&gt;&gt;。 RxSwift中Observable的各种创建方法: //just() let observable1 = ...

    广告:从前段时间开始接触RxSwift,跟着网上教程一点点的敲一些基础代码,慢慢地接触到越来越深的知识点,记录在此>>>

    RxSwift中Observable的各种创建方法:

    //just()

    let observable1 = Observable.just(5)
    复制代码

    //of() //该方法接收可变的参数,但是参数的类型必须相同 //虽然没显示地说明类型,但是swift也会自动推断类型

    let observable2 = Observable.of("A", "B", "C")
    复制代码

    //from() //改方法需要接收一个数组类型的参数 //效果和上面的of相同

    let observable3 = Observable.from(["A", "B", "C"])
    复制代码

    //empty() //创建一个空的observable序列

    let observable4 = Observable<Int>.empty()
    复制代码

    //never() //创建一个永远不会停止发出Event的observable序列

    let observable5 = Observable<Int>.never()
    复制代码

    //error() //创建一个不会做任何操作,而是直接发送一个错误的observable序列

    enum MyError: Error {
     case A
     case B
    }
    let observable6 = Observable<Int>.error(MyError.A)
    复制代码

    //range() //下面两个样例的结果相同

    let observable7 = Observable.range(start: 1, count: 5)
    let observable8 = Observable.of(1,2,3,4,5)
    复制代码

    //repeatElement() //创建一个可以无限发出给定元素的observable序列

    let observable9 = Observable.repeatElement(1)
    复制代码

    //generate() //创建一个只有当所有条件都为true的时候才会给出动作的observable序列 //下面两个样例的结果相同

    let observable10 = Observable.generate(initialState: 0, condition: {$0<=10}, iterate: {$0+2})
    let observable11 = Observable.of(0,2,4,6,8,10)
    复制代码

    //create() //该方法接收一个block形式的参数,对每一个过来的订阅者进行处理

    let observable12 = Observable<String>.create {observer in
    //对订阅者发出next事件,且携带一个数据hello world
       observer.onNext("hello world")
        //对订阅者发出.complated事件
       observer.onCompleted()
        //一个订阅者会有一个Disposable类型的返回值,所以结尾要return一个Disposable
       return Disposables.create()
       }
    //测试订阅
    observable12.subscribe {
        print($0)
    }
    复制代码

    //deferred() //创建一个observable工厂,通过传入一个block来执行延迟observable序列的创建行为,而这个block里就是真正的实例化序列的对象的地方。

    var isOdd = true
      
    let factory = Observable<Int>.deferred {
        isOdd = !isOdd
       
       if isOdd {
            return Observable.of(1,3,5,7)
        }else {
           return Observable.of(2,4,6,8)
        }
    }
    //第一次订阅测试
    factory.subscribe { (event) in
        print("\(isOdd)", event)
    }
    //第二次订阅测试
    factory.subscribe { (event) in
       print("\(isOdd)", event)
    }
    复制代码

    //interval() //创建的observable序列每隔一段时间会发出一个索引数的元素,而且会一直发送下去 //下面方法每隔1秒发送一次,并且在主线程

    let observable13 = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
    observable13.subscribe { (event) in
       print(event)
    }
    复制代码

    //timer() //5秒后发出唯一一个元素0

    let observable14 = Observable<Int>.timer(5, scheduler: MainScheduler.instance)
    observable14.subscribe { (event) in
        print(event)
    }
    复制代码

    //5秒后创建一个序列,每隔1秒产生一个元素

    let observable15 = Observable<Int>.timer(5, period: 1, scheduler: MainScheduler.instance)
    observable15.subscribe { (event) in
       print(event)
    }
    复制代码
    展开全文
  • 官方针对这个例子,介绍了用RxSwift实现MVVM,一个是使用Driver 序列(针对UI界面的),另一个是Observable 序列。所以可以总结的知识点如下: MVVM的实现 Observable序列和Driver序列的区别 功能说明还是老样子,先...
  • 前段时间一直在玩RxSwift ,现在有很多的朋友在玩RxSwift,现在就简单的记录一下,怎么能快速的学习和掌握框架.RxSwift其实并不是很难,你看了我写的这篇你就明白了。 RxSwift 宝图镇博,你的小星星呢~~~~ 现在...
  • 函数响应式编程FRP 之前学习过Objective-C下的FRP第三方开源框架ReactiveCocoa,部分学习笔记: ...它可以在MVVM架构模式中充当着View(视图)层与ViewModel(视图模型)层之间的Binder(绑定者)角色,实现两个层之间...
  • RxSwift-MVVM

    2019-08-23 21:10:19
    学习RxSwift框架以来,似乎并没有真正使用过这个框架,下面就来看看,RxSwift具体能带来哪些便利。 一、登录页面 先看看效果: UI页面代码省略,下面只看数据UI是如何绑定的。 1、UISwitch和UILabel的绑定 ...
  • 理解 RxSwift:为什么要使用 RxSwift(一) 理解 RxSwift:实现原理(二) RxSwift 内部是如何运行的,Observable 与 Observer 之间存在什么关系,Operator 又是如何实现的,如果想彻底弄清楚 RxSwift,我们可以自己...
  • RxSwift使用教程

    2018-06-17 21:52:09
    前言RxSwift是Swift函数响应式编程的一个开源库,由Github的ReactiveX组织开发,维护。RxSwift的目的是让让数据/事件流和异步任务能够更方便的序列化处理,能够使用Swift进行响应式编程目前,RxSwift在Github上收到...
  • 2019独角兽企业重金招聘Python工程师标准>>> ...
  • 输入电话号码的区号和八位数主体号码,下面的Label实时更新最新的输入,如果区号为3位数,点击按钮,按钮名字变成“变!” Ps:目前还不会,同时满足上面两位,下面三位的的约束,以后会更新的 ...
  • 在学习了RxSwift官方的demo以及各种操作符后,对RxSwift会有一个大致的了解,但在实际开发过程中并不是有很多机会去使用,主要是因为使用生疏的开发技能会带来开发时间上与产品质量上的风险,为了避免”不熟悉-&...
  • 在使用RxSwift的时候,ViewMode 与View之间的绑定事件,我们都知道,许多 UI 控件(switch, datePicker, textView, textField)既是可被监听的序列也是观察者,这种特性,我们只需要在View里面绑定一下,ViewModel ...
  • RxSwift处理Error事件

    2020-04-28 15:48:06
    如何处理RxSwift的Error事件 翻译自:How to handle errors in RxSwift 在最近这些日子里,MVVM在iOS开发中变得越来约受欢迎,RxSwfit也变得越来越流行。在RxSwift中大多数属性都是序列(Observable)。 但是,当...
  • RxSwift + MVVM 初体验

    2019-09-11 07:17:44
    一、原起 作为一名iOS开发者,必须跟上时代的潮流,随着...最近使用RxSwift+MVVM+Moya进行了swift的体验之旅。加入到swift开发的大潮中去。 二、目录结构 这个demo的项目结构包括:View、Model、ViewModel、Cont...
  • 一 什么是RxSwiftRxSwift做了什么 2-1简单介绍观察者设计模式 2-1RxSwift做了什么 2-3 简单理解Observable&Observer 三 RxSwift初级操作 3-1 监听事件 3-2 监听文本输入框的文字改变 3-3 绑定...
  • 【iOS】仿知乎日报,RxSwift-Part1-首页搭建 2017年10月21日 09:42:42 阅读数:738 前言 之前的几篇博客算是入门篇,那么这篇就是RxSwift的实战篇。由于对RxSwift的认识还不够深刻,所以项目中没有使用MVVM模型...
1 2 3 4 5 ... 20
收藏数 525
精华内容 210
热门标签