mvvm绑定 swift

2020-04-12 17:12:00 u014158743 阅读数 275

Associate what you’ re trying to learn with what you already know. —— Daily English

这篇文章旨在通过一个Demo让我们对Android中的MVVM架构进行初步的认识。

MVVM与DataBinding的关系

很多同学会将这两者混为一谈,所以开始介绍之前,我们需要先理清楚这两者的关系。
MVVM和MVC、MVP一样,是项目中的架构设计思想;DataBinding是一种工具,它可以用于MVVM,也同样可以用于MVC和MVP。所以这两者是两回事,一个是架构设计思想,一个是工具。但是有一点,那就是Android中的MVVM一般都需要借助DataBinding来实现,这也是很多人将这两者混为一谈的原因。

MVVM简介

MVVM是更节省的设计模式,能实现双向的数据绑定。

须知

MVVM可以理解成M V VM。其中的M指的是Model层,也就是我们的JavaBean。V指的是VIew层,也就是我们具体的布局,如EditText等。VM指的是ViewModel层,它是Model层和View层的一个桥梁,也用来处理视图逻辑和业务逻辑。
简而言之,M还是Model,V还是View,VM就是ViewModel层。三者的关系大致如下图所示:
在这里插入图片描述
这个架构模式有如下两个特点

  1. 降低耦合:一个ViewModel层可以绑定不同的View层,当Model变化时View可以不变。
  2. 可重用性:可以把一些视图逻辑放在ViewModel层中,让很多View重用这些视图逻辑。

ViewModel相当于model层和View层的一个桥梁,当View层比如说一个EditText的值发生改变了,无需通过Activity,就直接可以改变JavaBean对应的属性值。Model层set一个值,也无需通过Activity,就可以直接改变页面上的值。

MVVM是有弊端的,一个是修改之后需要经常ReBuild,而且项目越大,ReBuild的时间也越长。另外也有三个原因会导致它的内存消耗比较大,这个会在介绍DataBinding的时候讲到(点击查看)。这也是有些公司不愿意用MVVM架构的原因。但是,MVVM为什么还会这么火呢,就是因为这种View和Model的双向绑定思想是值得我们学习的,也很可能是一种趋势。

什么是单向数据绑定,什么是双向数据绑定。

单向绑定是指View层(如EditText)上的数据改变会实时更新到Model层JavaBean中对应的属性值(如username)上。或者,Model层的数据改变会实时更新到View层上的显示,这样我们称之为单向的数据绑定。而双向绑定呢,是指Model层和View层,无论那层数据改变都会实时更新到对方,Model层数据改变会更新View,同样,View层数据改变会更新Model,这样就称之为双向的数据绑定。

项目实践

模拟一个登录的功能。

第一步,引入
在module的build.gradle文件中引入DataBinding

android {
	...
	// 添加DataBinding依赖
	dataBinding{
	    enabled = true
	}
)

第二步,定义实体类

public class UserInfo {
    // 被观察的属性(切记:必须是public修饰符,因为是DataBinding的规范)
    public ObservableField<String> name = new ObservableField<>();

    public ObservableField<String> pwd = new ObservableField<>();
}

实体类也可以定义成原始的那种格式,添加get(),set()方法,也可以定义成被观察者属性的格式。只要注意两点,一个是被观察者属性,一个是有刷新属性的方法。这里就不做解释,对格式有疑问的同学请参考我另一篇讲DataBinding的文章(点击查看)。

第三步,定义布局

布局界面很简单
在这里插入图片描述
对应xml文件代码如下

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <!--定义该布局需要绑定的数据名称和类型-->
    <data>
        <variable
            name="loginViewModel"
            type="pers.owen.my_mvvm.vm.LoginViewModel" />
    </data>

    <!-- 下部分内容和平时布局文件一样 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="20dp"
            android:hint="请输入账户"
            android:text="@={loginViewModel.userInfo.name}" />

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="20dp"
            android:hint="请输入密码"
            android:text="@={loginViewModel.userInfo.pwd}" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:onClick="@{loginViewModel.loginClickListener}"
            android:text="登录" />

    </LinearLayout>
</layout>

布局格式参考上述XML即可。

这里我们发现<data>中的variable是一个ViewModel的名称和具体路径,当然也可以直接是一个实体类。这也就是我们为什么说DataBinding其实也可以用在MVC或MVP架构当中,因为这篇文章讲的是MVVM的架构,所以这里定义一个专门处理登录逻辑的ViewModel,叫做LoginViewModel类。

其中如android:onClick="@{loginViewModel.loginClickListener}"loginViewModel就是<data>标签下LoginViewModel的name。可以把这里的loginViewModel理解成一个对象,而loginViewModel.loginClickListener就是相当于调用这个对象中一个叫loginClickListener的public方法。

LoginViewModel的方法随即附上。

第四步,定义ViewModel

public class LoginViewModel {
    public UserInfo userInfo;

    public View.OnClickListener loginClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // 尝试1:view --> model单向绑定测试,改变EditText的值,查看bean中的对应属性值是否发生变化。
//            Log.e("owen >>> ", userInfo.name.get() + "--" + userInfo.pwd.get());

            // 尝试2:model --> view单向绑定测试,Model层属性的变更,也会改变View层的显示
//            userInfo.name.set("Owen");
//            userInfo.pwd.set("0410");

            // 尝试3:模拟网络请求
            new Thread(new Runnable() {
                @Override
                public void run() {
                    if ("Owen".equals(userInfo.name.get()) && "123".equals(userInfo.pwd.get())) {
                        Log.e("Owen >>> ", "登录成功!");
                    } else {
                        Log.e("Owen >>> ", "登录失败!");
                    }
                }
            }).start();
        }
    };
}

注意LoginViewModel类中的属性名userInfologinClickListener是要和xml布局中如android:onClick="@{loginViewModel.loginClickListener}"的名称对应上。可以通过ctrl+左键点击跳转验证。

第五步,Rebuild Project
Rebuild完成后,会在data_binding_base_class_source_out目录下生成以[布局名]Binding.java文件,这里我们的布局叫activity_main,所以生成了一个名为ActivityMainBinding的Java类文件。目录的具体位置如下图所示:
在这里插入图片描述
有了它,我们就可以做绑定操作了。

最后一步,书写代码绑定

在Activity中创建ActivityMainBinding对象

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 1、必须先ReBuilder,2、书写代码绑定
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        new LoginViewModel(binding);
    }
}

通过构造方法传入LoginViewModel进行绑定

 public LoginViewModel(ActivityMainBinding binding) {
     userInfo = new UserInfo();
     // 将ViewModel和View进行绑定,通过DataBinding工具。
     binding.setLoginViewModel(this);
 }

最后LoginViewModel的完整代码如下:

public class LoginViewModel {
    public UserInfo userInfo;

    public LoginViewModel(ActivityMainBinding binding) {
        userInfo = new UserInfo();
        // 将ViewModel和View进行绑定,通过DataBinding工具。
        binding.setLoginViewModel(this);
    }

    public View.OnClickListener loginClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // 尝试1:view --> model单向绑定测试,改变EditText的值,查看bean中的对应属性值是否发生变化。
//            Log.e("owen >>> ", userInfo.name.get() + "--" + userInfo.pwd.get());

            // 尝试2:model --> view单向绑定测试,Model层属性的变更,也会改变View层的显示
//            userInfo.name.set("Owen");
//            userInfo.pwd.set("0410");

            // 尝试3:模拟网络请求
            new Thread(new Runnable() {
                @Override
                public void run() {
                    if ("Owen".equals(userInfo.name.get()) && "123".equals(userInfo.pwd.get())) {
                        Log.e("Owen >>> ", "登录成功!");
                    } else {
                        Log.e("Owen >>> ", "登录失败!");
                    }
                }
            }).start();
        }
    };
}

尝试代码依次开启,做测试
尝试1:在界面上输入用户名,密码,点击登录,查看log发现UserInfo中的两个属性值已经被改变。证明View --> Mode单向绑定是Ok的。
尝试2:程序运行后,直接点击登录,发现界面上的EditText上的值已经更改。证明Model --> View的单向绑定也是成功的。
尝试3:模拟网络登录。

这就是我们在业务中用到的MVVM,我们发现在Activity中什么事情都不用干,但是我们必须有一个VM来作为V和M的桥梁来沟通。我们现在不需要在Activity中做很多复杂的东西,这就是架构MVVM的思想。你学到了吗?


文中Demo下载地址

Android 架构设计模式系列文章索引

MVC架构设计与经典的三层模型

MVVM实现数据双向绑定

DataBinding的使用与原理

2016-03-04 10:46:58 u013406800 阅读数 1908

MVVM是微软提出一种移动开发框架,旨在针对传统的MVC框架,解决传统的MVC框架的控制器的臃肿问题.

M:

Model模型,也就是数据模型;比如一条微博,对应的所有字段合成一条微博整体,这个整体就是Model


V:

View视图,只用来显示的视图,如 iOS的UIView,Cell;当然在 iOS中 Storyboard中,view总是和控制器关联,这并不是严格的view

如果我们纯手写代码定义一个view那么就是一个比较严格的view了


VM:

ViewModel视图模型,是将一个 View 和 Model进行绑定;起着桥梁的作用

例如一个 微博  cell ,对应的数据绑定到一个 Model,将获得的数据绑定到Model个各个属性上.


传统的MVC中的  C代表 控制器,负责逻辑处理 和 数据绑定,更新视图;代码过于复杂,维护会比较困难;

而MVVM框架则是,给 Controller控制器减负;

把网络请求,数据绑定,更新视图,都剥离出去;

是在 Controller中做的仅仅是简单的跳转和部分代理


比较符合MVVM开发框架的是 使用TableView的控制器;Cell自定义;定义所需的Model;定义ViewModel调用对应的接口


下面来示范一个MVVM框架开发,带有网络请求的Demo



目录说明:

Resourse:存放图片,音频视频文件,IB文件

Controllers:存放视图控制器

Vender:开源框架,或者自己封装的组件

UtilTools:封装的常用代码,常量定义,配置文件等

ViewModel:根据不同的View获取不同的数据并且绑定到Model中

View:自定义个视图如 cell或者纯手写的view

Model:数据模型,就是View所需的所有字段的整体


实现效果:

一个tableView上显示多条动态包括,图片,文字;



技术要点:

MVVM框架

AFNetworking网络请求

MBProgressHUD等待提示

自定义cell

Block闭包传值,封装代码


实现过程:

1.导入Oc的相关框架


在Swift项目中新建一个Oc文件,系统询问你是否新建一个 桥接Oc的文件,选择是,生成一个  Header.h文件;在其中导入 对应的Oc的 头文件即可使用

如图,工程中有一个 SwiftDemo-Bridging-Header.h文件

导入AF等框架

SwiftDemo-Bridging-Header.h 

  1. //  

  2. //  Use this file to import your target's public headers that you would like to expose to Swift.  

  3. //  

  4.   

  5.   

  6.   

  7.   

  8. #import "AFNetworking.h"  

  9. #import "MBProgressHUD.h"  

  10. #import "UIImageView+AFNetworking.h"  


2.使用Storyboard创建一个自定义的cell绑定到cell类,标识符是 TestCell




绑定的TestCell.swift类

  1. //  

  2. //  TestCell.swift  

  3. //  SwiftDemoUsingAF  

  4. //  

  5. //  Created by MBinYang on 15/4/8.  

  6. //  Copyright (c) 2015年 cc.huanyouwang. All rights reserved.  

  7. //  

  8.   

  9. import UIKit  

  10.   

  11. class TestCell: UITableViewCell  

  12. {  

  13.   

  14.     @IBOutlet var imageView2: UIImageView!  

  15.     @IBOutlet var textLabel2: UILabel!  

  16.     @IBOutlet var location: UILabel!  

  17.     override func awakeFromNib()  

  18.     {  

  19.         super.awakeFromNib()  

  20.           

  21.    }  

  22.   

  23.     override func setSelected(selected: Bool, animated: Bool)  

  24.     {  

  25.         super.setSelected(selected, animated: animated)  

  26.           

  27.    }  

  28.       

  29.    /** 

  30.     *配置Cell的内容方法 

  31.   */  

  32.     func configCellWithStatusModel(model:StatusModel!)  

  33.     {  

  34.         self.imageView2.setImageWithURL(NSURL(string: model.imageURL), placeholderImage: UIImage(named: "placeHolder"))  

  35.         self.textLabel2.text = model.text  

  36.         self.location.text = model.location  

  37.     }  

  38.   

  39. }  



Cell上包含一个 imageView和两个label


3.常量定义,类似Oc的宏定义


StringConstant.swift

常量以小写k开头,其余大写;可以配上下划线

Swift的常量,使用 let定义常量;可以在其他地方使用

  1. //  

  2. //  StringConstant.swift  

  3. //  SwiftDemo  

  4. //  

  5. //  Created by MBinYang on 15/4/8.  

  6. //  Copyright (c) 2015年 cc.huanyouwang. All rights reserved.  

  7. //  

  8.   

  9. import Foundation  

  10. import UIKit  

  11.   

  12.   

  13. let kGETSTATUSURL = "http://lovemyqq.sinaapp.com/getState.php"  

  14. let kACCESSTOKEN = "2.00HZE3aF0GvJSM551ca8e0920NF13N"  

  15. let kUID = "5117873025"  

  16. let kACCESSTOKENKEY = "access_token"  

  17. let kUIDKEY = "uid"  

  18. let kPARAMDIC = [kACCESSTOKENKEY : kACCESSTOKEN,kUIDKEY : kUID]  

  19. let kREDCOLOR = UIColor.redColor  

  20. let kIMAGEKEY = "image"  

  21. let kDATEKEY = "date"  

  22. let kLOCATIONKEY = "address"  

  23. let kCONTENTKEY = "content"  

  24. let kRESULTKEY = "result"  

  25. let kSTATEKEY = "state"  

  26.   

  27.   

  28. //其他复杂类型,可以使用函数定义  

  29. //func RGBA (r:CGFloat, g:CGFloat, b:CGFloat, a:CGFloat)  

  30. //{  

  31. //    return UIColor (red: r/255.0, green: g/255.0, blue: b/255.0, alpha: a)  

  32. //}  

  33.   

  34.   

  35.   

  36. //使用方法如下  

  37. //var theColor : UIColor = RGBA (255, 255, 0, 1)  



4.封装AF为一个常用的方法,便于ViewModel调用


AFRequest.swift

  1. //  

  2. //  AFRequest.swift  

  3. //  SwiftDemo  

  4. //  

  5. //  Created by MBinYang on 15/4/8.  

  6. //  Copyright (c) 2015年 cc.huanyouwang. All rights reserved.  

  7. //  

  8.   

  9. import UIKit  

  10.   

  11. public typealias SuccessBlock = (obj:AnyObject)->Void!  

  12. class AFRequest: NSObject  

  13. {  

  14.     var mainURL:String!  

  15.     var paramDict:[String:String]!  

  16.     var successBlock:SuccessBlock!  

  17.     var manager:AFHTTPRequestOperationManager  

  18.     var hud:MBProgressHUD!  

  19.   

  20.      

  21.     init(mainURL:String,paramDict:[String:String],successBlock:SuccessBlock)  

  22.     {  

  23.         self.mainURL = mainURL  

  24.         self.paramDict = paramDict  

  25.         self.successBlock = successBlock  

  26.         self.manager = AFHTTPRequestOperationManager()  

  27.         super.init()  

  28.     }  

  29.   

  30.       

  31.     func startRequestWithHUDOnView(view:UIView!)  

  32.     {  

  33.         self.hud = MBProgressHUD.showHUDAddedTo(view, animatedtrue)  

  34.         self.hud.labelText = "正在请求..."  

  35.    

  36.        var op =  self.manager.GET(self.mainURL,  

  37.             parametersself.paramDict,  

  38.             success: {  (operation: AFHTTPRequestOperation!,  

  39.                 responseObject: AnyObject!) in  

  40.                   

  41.                 var arr: AnyObject! = NSJSONSerialization.JSONObjectWithData(responseObject as NSData, options: NSJSONReadingOptions.AllowFragments, error: nil)  

  42.                   

  43.                 println(arr!)  

  44.                 unowned var unSelf:AFRequest = self  

  45.                unSelf.successBlock(obj: arr!)  

  46.                unSelf.hud.customView = nil  

  47.                unSelf.hud.labelText = "加载成功!"  

  48.                unSelf.hud.hide(false, afterDelay3)  

  49.             },  

  50.             failure: {  (operation: AFHTTPRequestOperation!,  

  51.                 error: NSError!) in  

  52.                 println("请求错误Error: " + error.localizedDescription)  

  53.                 unowned var unSelf:AFRequest = self  

  54.                 unSelf.hud.hide(true)  

  55.         })  

  56.           

  57.         op.responseSerializer = AFHTTPResponseSerializer()  

  58.         op.start()  

  59.     }  

  60.       

  61.       

  62. }  



5.根据View/Cell所需的数据,封装一个Model类.


如 封装一个 和 Cell数据字段对应的Model类  StatusModel状态模型

StatusModel.swift

包含,属性,和存取器

  1. //  

  2. //  StatusModel.swift  

  3. //  SwiftDemoUsingAF  

  4. //  

  5. //  Created by MBinYang on 15/4/8.  

  6. //  Copyright (c) 2015年 cc.huanyouwang. All rights reserved.  

  7. //  

  8.   

  9. import UIKit  

  10.   

  11. class StatusModel: NSObject  

  12. {  

  13.       

  14.     var imageURL:String!  

  15.     var text:String!  

  16.     var location:String!  

  17.       

  18.       

  19.       

  20.   override  init()  

  21.     {  

  22.         //Todo  

  23.     }  

  24.       

  25.       

  26.       

  27.     init(imageURL:String!,text:String,location:String)  

  28.     {  

  29.         self.imageURL = imageURL  

  30.         self.text = text  

  31.         self.location = location  

  32.     }  

  33.       

  34.     var configModel:(String!,String!,String!)  

  35.     {  

  36.         set  

  37.         {  

  38.             self.imageURL = newValue.0  

  39.             self.text = newValue.1  

  40.             self.location = newValue.2  

  41.         }  

  42.         get  

  43.         {  

  44.             return (self.imageURL,self.text,self.location)  

  45.         }  

  46.     }  

  47.       

  48.     //省略set的 简写的 get  

  49.     var urlOfImg:String  

  50.     {  

  51.         return self.imageURL  

  52.     }  

  53.      

  54. }  



6.ViewModel根据View所需数据,调用对应的接口;并且把数据绑定到Model中


解析JSON数据,绑定到Model中

StatusViewModel.swift

  1. //  

  2. //  StatusViewModel.swift  

  3. //  SwiftDemo  

  4. //  

  5. //  Created by MBinYang on 15/4/8.  

  6. //  Copyright (c) 2015年 cc.huanyouwang. All rights reserved.  

  7. //  

  8.   

  9. import UIKit  

  10.   

  11. public class StatusViewModel: NSObject  

  12. {  

  13.   

  14.     func requestStatusList(view:UIView!,blk:SuccessBlock)  

  15.     {  

  16.           

  17.     var af = AFRequest(mainURL: kGETSTATUSURL, paramDict: kPARAMDIC)  

  18.         { (obj) -> Void! in  

  19.               

  20.               

  21.             var dicO = obj as Dictionary<String,AnyObject>  

  22.             var arr1: AnyObject? = dicO[kRESULTKEY]  

  23.             var  arrreal = arr1 as Array<Dictionary<String,Dictionary<String,String>>>  

  24.             var modelArray = Array<StatusModel>()  

  25.               

  26.             /** 解析得到的JSON数据为Model数组*/  

  27.             for dict in arrreal  

  28.             {  

  29.                 var dic = dict[kSTATEKEY]! as Dictionary<String,String>  

  30.                 var sModel = StatusModel()  

  31.                 sModel.imageURL = dic[kIMAGEKEY]!  

  32.                 sModel.text = dic[kCONTENTKEY]!  

  33.                 sModel.location = dic[kLOCATIONKEY]!  

  34.                 modelArray.append(sModel)  

  35.                   

  36.             }  

  37.             println(modelArray[0])  

  38.            blk(obj: modelArray)  

  39.               

  40.             return Void()  

  41.         }  

  42.     af.startRequestWithHUDOnView(view)  

  43.     

  44.    }  

  45.      

  46. }  



7.TableViewController 控制器中调用  ViewModel显示数据


  1. //  

  2. //  TableViewController.swift  

  3. //  SwiftDemo  

  4. //  

  5. //  Created by MBinYang on 15/4/8.  

  6. //  Copyright (c) 2015年 cc.huanyouwang. All rights reserved.  

  7. //  

  8.   

  9. import UIKit  

  10.   

  11. class TableViewController: UITableViewController  

  12. {  

  13.   

  14.     var modelArray = Array<StatusModel>()  

  15.       

  16.     override func viewDidLoad()  

  17.     {  

  18.         super.viewDidLoad()  

  19.           

  20.          loadData()  

  21.           

  22.     }  

  23.   

  24.     override func didReceiveMemoryWarning()  

  25.     {  

  26.         super.didReceiveMemoryWarning()  

  27.           

  28.     }  

  29.   

  30.   

  31.     override func numberOfSectionsInTableView(tableView: UITableView) -> Int  

  32.     {  

  33.           

  34.         return 1  

  35.     }  

  36.   

  37.     override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int  

  38.     {  

  39.         return modelArray.count  

  40.     }  

  41.   

  42.       

  43.     override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell  

  44.     {  

  45.           

  46.       var sModel = self.modelArray[indexPath.row]  

  47.         var cell = tableView.dequeueReusableCellWithIdentifier("TestCell", forIndexPath: indexPath) as TestCell  

  48.   

  49.         cell.configCellWithStatusModel(sModel as StatusModel)  

  50.   

  51.         return cell  

  52.     }  

  53.       

  54.    /** 

  55.     * 第一次加载数据时请求 

  56.     * 返回为空 

  57.     */  

  58.     func loadData()  

  59.     {  

  60.         StatusViewModel().requestStatusList(self.view, blk: { (obj) -> Void! in  

  61.             unowned var weak_self:TableViewController = self  

  62.             println("这是第一次加载数据的 block\(__FUNCTION__)")  

  63.             weak_self.modelArray = obj as Array<StatusModel>  

  64.             weak_self.tableView.reloadData()  

  65.             return Void()  

  66.         })  

  67.     }  

  68.   

  69. }  


调用ViewModel之后再  reloadData()


当然,ViewModel也可能是 在 View上调用的;比如 Cell上有一个  按钮,点击按钮会进行网络请求,也需要调用 ViewModel


8.总结


我们可以看到 在 TableViewController 中,代码非常的少.原来对应  网络请求,对于 视图更新都是放在 controller中;

使用MVVM后可以大大减少Controller的代码,是的各部分耦合性更低,更便于维护.



9.其他,view上有事件的触发


如果 Cell或者 View上或者有 点击事件,或者其他事件;可以 使用 闭包封装 ;类似于 上面的 AF请求.

2019-03-03 12:40:14 wf96390 阅读数 2440

函数响应式编程FRP

之前学习过Objective-C下的FRP第三方开源框架ReactiveCocoa,部分学习笔记:
https://blog.csdn.net/wf96390/article/details/50933421
它可以在MVVM架构模式中充当着View(视图)层与ViewModel(视图模型)层之间的Binder(绑定者)角色,实现两个层之间的同步更新。在ReactiveCocoa的世界中,数据与属性的改变、视图的操作反馈、方法的调用等都可以被监听并抽象转换成事件流,封装在Signal(信号)中,我们通过对Signal的Subscribe(订阅)就能获取到其中的事件流,并进行相应的操作。

函数式编程 Functional programming

总结一下函数式编程具有以下几个特点:

  • 函数是“第一等公民”
  • 闭包和高阶函数
  • 只用“表达式”,不用“语句”,不改变状态,没有副作用
  • 柯里化

Haskell是一种标准化的、通用纯函数式编程语言,有非限定性语义和强静态类型。

响应式编程

var b = 1
let c = 2
let a = b  + c  // a = 3

// 改变 b 的值,a的值会变吗?
b = 3
print(a)

在命令式编程环境中, a = b + c 表示将表达式的结果赋给 a,而之后改变 b 或 c的值不会影响 a。
但在响应式编程中,a的值会随着 b或 c的更新而更新。

RxSwift

RxSwift是Reactive下的一个分支,Reactive涵盖了很多语言。

RxSwift为ReactiveX(Reactive Extensions)旗下的Swift语言库,提供了Swift平台上进行响应式编程的解决方案。Rx的重要角色为Observable(被观察者)和Observer(观察者),Observable类似于ReactiveCocoa中的Signal,里面装有事件流,供Observer订阅。事件流在Rx中与ReactiveCocoa一样具有三类:Next、Error、Completed,代表着继续事件、错误事件、完成事件。我们在使用RxSwift进行iOS开发时,通常会引入另外一个库:RxCocoa,这个库将UIKit以及Foundation框架中许多成员,如视图(View)、控制事件(Control Event)、键值观察(KVO)、通知(Notification)等等进行与RxSwift接入的扩展,将Rx与iOS API无缝连接。

Observable 和 Observer

Observable指的是可被观察者或者事件源。Observable是一种像信号一样的序列,可以理解为一串数组,但是需要有人监听这些信号。ObservableType协议中定义的subscribe函数就是一种简便的订阅信号的一种方法。这里的subscribe函数就是把消息发给观察者。

实现了 ObservableType 协议, ObservableType 协议中定义了很多方法。

public class Observable<Element> : ObservableType {
    public func subscribe<O>(_ observer: O) -> Disposable where Element == O.E, O : ObserverType

    public func asObservable() -> RxSwift.Observable<RxSwift.Observable<Element>.E>
}

Observer : 观察者,观察者需要订阅Observable,才能受到其发出的事件

subscribe :用于订阅sequence发出的事件,相当于Swift序列中的遍历操作(makeIterator)

subscribe(_:)方法接受事件参数,下面这个例子接受元素参数
someObservable.subscribe(
    onNext: { print("Element:", $0) },
    onError: { print("Error:", $0) },
    onCompleted: { print("Completed") },
    onDisposed: { print("Disposed") }
)

Dispose

在使用subscribe订阅一个可观察序列时,会返回一个Disposable类型的对象。这里的Disposable是一个协议,只定义了一个方法:

public protocol Disposable {
    /// Dispose resource.
    func dispose()
}

DisposeBag : 类似于 iOS 中的 ARC,会在适当的时候销毁观察者,自动去释放资源。需要调用 disposed 方法,加入到 disposed 中。如果未添加 Xcode 会提示报错。

let disposeBag = DisposeBag()

extension Disposable {

    /// Adds `self` to `bag`
    ///
    /// - parameter bag: `DisposeBag` to add `self` to.
    public func disposed(by bag: RxSwift.DisposeBag)
}

如果需要手动销毁观察者直接调用dispose()方法

Observable.empty().subscribe { (str: Event) in 
    print(“empty:”, str) 
}.dispose()

常见用法

Observable 类似 swift 中的序列,可以用数据流图表示
https://rxmarbles.com/

创建Observable

  • never

构建一个从不终止和发出任何事件的队列

Observable<String>.never().subscribe { _ in
   print("This will never be printed")
}
.disposed(by: disposeBag)
  • empty

构建一个空的Observable队列,只发出完成事件

 Observable<Int>.empty()
     .subscribe { event in
         print(event)
     }
     .disposed(by: disposeBag)
  • just

构建一个只有一个元素的Observable队列

Observable.just("123")
   .subscribe { event in
       print(event)
    }
    .disposed(by: disposeBag)
  • of

构建一个拥有固定数量元素的Observable序列

Observable.of("A", "B", "C", "D")
    .subscribe(onNext: { element in
        print(element)
    })
    .disposed(by: disposeBag)
  • from

从序列中创建可观察到的序列,如数组、字典或集合。

Observable.from(["?", "?", "?", "?"])
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)
  • create

构建一个自定义的可观察序列

  let myJust = { (element: String) -> Observable<String> in
      return Observable.create { observer in
          observer.on(.next(element))
          observer.on(.completed)
          return Disposables.create()
      }
  }
 myJust("?")
     .subscribe { print($0) }
     .disposed(by: disposeBag)
  • range

创建一个可观察序列,该序列释放一系列连续整数,然后终止

 Observable.range(start: 1, count: 10)
    .subscribe { print($0) }
    .disposed(by: disposeBag)
  • repeatElement

创建一个可观察到的序列,它无限地释放给定的元素

Observable.repeatElement("123")
     .take(3)
     .subscribe(onNext: { print($0) })
     .disposed(by: disposeBag)
  • generate

创建一个可观察的序列,只要所提供的条件求值为true,就生成值。

 Observable.generate(
        initialState: 0,
        condition: { $0 < 3 },
        iterate: { $0 + 1 }
    )
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)
  • error

创建一个没有任何元素的可观察序列,并立即以错误结束。

 Observable<Int>.error(TestError.test)
    .subscribe { print($0) }
    .disposed(by: disposeBag)

创建Subject

Subjet 类似于ReactiveCocoa中的热信号,可以主动发送信号,也可以被监听,一个Subject既可以充当Obserable也可以充当Observer,即它可以发出事件,也可以监听事件。
在被订阅以后,订阅者可以监听到订阅之后发生的事件,像收音机发送广播一样,打开收音机以后可以听到当前时间以及之后的广播,之前未收听到能否接收的根据Subject类型判断。
几种 Subject 都是 RxSwift.Observable 的子类。

  • PublishSubject

采用PublishSubject订阅事件的时候,只能接收到订阅他之后发生的事件,完全和收音机听广播一样。

let publicSubject = PublishSubject<String>()
publicSubject.onNext("A")
publicSubject.onNext("B")
publicSubject.subscribe(onNext: {print("publicSubject:",$0)}).addDisposableTo(bag)
publicSubject.onNext("C")
publicSubject.onNext("D")

/**
 publicSubject: C
 publicSubject: D
 **/
  • ReplaySubject

可以接收到订阅他之后以及之前发生的事件, 对于要接受几个以前的事件取决于bufferSize设置的大小。

let replaySubject = ReplaySubject<String>.create(bufferSize: 2)
// 如果需要接受全部的事件,则可以使用
// let replaySubject = ReplaySubject<String>.createUnbounded()

replaySubject.onNext("A")
replaySubject.onNext("B")
replaySubject.onNext("C")
replaySubject.onNext("D")
replaySubject.subscribe(onNext: {print("replaySubject:",$0)}).addDisposableTo(bag)
replaySubject.onNext("E")
replaySubject.onNext("F")
replaySubject.onNext("G")
/**
 replaySubject: C
 replaySubject: D
 replaySubject: E
 replaySubject: F
 replaySubject: G
 **/
  • BehaviorSubject

采用BehaviorSubject订阅事件,会接收到订阅之前的最后一个事件以及订阅之后所有事件。如果在 BehaviorSubject 创建后马上监听,会接收到初始值和订阅之后的事件。

let behavior = BehaviorSubject(value: "behavior")
behavior.onNext("A")
behavior.onNext("B")
behavior.subscribe(onNext: {print("behavior:",$0)}).addDisposableTo(bag)
behavior.onNext("C")
behavior.onNext("D")

/**
 behavior: B
 behavior: C
 behavior: D
 **/
  • Variable

Variable 是对 BehaviorSubject 的一个包装。BehaviorSubject 不会因为错误而终止, 但是 variable 会被释放。
Variable当成Obserable, 让订阅者进行订阅时, 需要asObserable转成Obserable;

Variable发出事件, 直接修改对象的value即可;

当事件结束时,Variable会自动发出completed事件

This concept will be deprecated from RxSwift but offical migration path hasn’t been decided yet.
https://github.com/ReactiveX/RxSwift/issues/1501

Current recommended replacement for this API is RxCocoa.BehaviorRelay because:

  • Variable isn’t a standard cross platform concept, hence it’s out of place in RxSwift target.
  • It doesn’t have a counterpart for handling events (PublishRelay). It models state only.
  • It doesn’t have a consistent naming with *Relay or other Rx concepts.
  • It has an inconsistent memory management model compared to other parts of RxSwift (completes on deinit).
let variable = Variable("variable")

variable.value = "A"
variable.value = "B"
variable.asObservable().subscribe { (event:Event<String>) in
    print("variable:",event)
    }.addDisposableTo(bag)
variable.value = "C"
variable.value = "D"

/**
 variable: next(B)
 variable: next(C)
 variable: next(D)
 variable: completed
 **/

组合操作

可以将多个Observables组合成单个的Observable

  • startWith

在Observable源发出元素前,发出特定的元素

Observable.of("2", "3")
    .startWith("1️⃣")
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

在这里插入图片描述

  • merge

将源可观察序列的元素组合成一个新的可观察序列

let subject1 = PublishSubject<String>()
let subject2 = PublishSubject<String>()
    
Observable.of(subject1, subject2)
    .merge()
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)
    
subject1.onNext("?️")
subject1.onNext("?️")
subject2.onNext("①")
subject2.onNext("②")
subject1.onNext("?")
subject2.onNext("③")

在这里插入图片描述

  • zip

将可观察序列合并成一个新的可观察序列

let stringSubject = PublishSubject<String>()
let intSubject = PublishSubject<Int>()
    
Observable.zip(stringSubject, intSubject) { stringElement, intElement in
    "\(stringElement) \(intElement)"
    }
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)
    
stringSubject.onNext("?️")
stringSubject.onNext("?️")
    
intSubject.onNext(1)
intSubject.onNext(2)

stringSubject.onNext("?")
intSubject.onNext(3)

在这里插入图片描述

  • combineLatest

将可观察队列合并成单个新的可观察队列,当多个源至少有一个的时候发出信号

let stringSubject = PublishSubject<String>()
let intSubject = PublishSubject<Int>()

Observable.combineLatest(stringSubject, intSubject) { stringElement, intElement in
        "\(stringElement) \(intElement)"
    }
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

stringSubject.onNext("?️")

stringSubject.onNext("?️")
intSubject.onNext(1)

intSubject.onNext(2)

stringSubject.onNext("?")

在这里插入图片描述
当然可以将数组作为参数

let stringObservable = Observable.just("❤️")
let fruitObservable = Observable.from(["?", "?", "?"])
let animalObservable = Observable.of("?", "?", "?", "?")

Observable.combineLatest([stringObservable, fruitObservable, animalObservable]) {
        "\($0[0]) \($0[1]) \($0[2])"
    }
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

如果你采用zip组合,那必须两个label都有值改变才会触发,如果使用combineLatest,则其中一个改变就会出发。

转换操作

转换可观察队列发出的Next事件里元素的操作

  • map

将转换闭包应用于可观察序列发出的元素,并返回已转换元素的一个新的可观察序列。

let disposeBag = DisposeBag()
Observable.of(1, 2, 3)
    .map { $0 * $0 }
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

在这里插入图片描述

  • flatMap

将Observable队列发出的元素转换成一个新的可观察队列,并将两者的输出组合成一个新的可观察队列,意思就是说会监听原队列,也会监听你闭包中转换过的队列。

struct Player {
    var score: Variable<Int>
}

let ?? = Player(score: Variable(80))
let ?? = Player(score: Variable(90))

let player = Variable(??)

player.asObservable()
    .flatMap { $0.score.asObservable() } // Change flatMap to flatMapLatest and observe change in printed output
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

??.score.value = 85  // 会有响应

player.value = ??    // 这边也会有响应  监听了两个组合队列

??.score.value = 95 // Will be printed when using flatMap, but will not be printed when using flatMapLatest

??.score.value = 100
  • scan

以初始值开始,然后将一个累加器闭包应用于可观察序列所发射的每个元素,并将每个中间结果作为一个元素可观察序列返回。看例子

Observable.of(10, 100, 1000)
    .scan(1) { aggregateValue, newValue in    // aggregateValue 之前返回的值
        aggregateValue + newValue
    }
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)    //  11,111,1111

在这里插入图片描述

过滤操作

  • filter

类似于 swift 语法中的filter,筛选出符合条件的信号

Observable.of(
    "?", "?", "?",
    "?", "?", "?",
    "?", "?", "?")
    .filter {
        $0 == "?"
    }
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

在这里插入图片描述

  • distinctUntilChanged
    直到信号变化时,才发出信号,比如使用 textfield 只有内容变化时才发出信号
Observable.of("?", "?", "?", "?", "?", "?", "?")
    .distinctUntilChanged()
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)    //?????

在这里插入图片描述

  • elementAt
    获取序列中的第某个信号
Observable.of("?", "?", "?", "?", "?", "?")
    .elementAt(3)
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)   //   ?

在这里插入图片描述

  • single

序列发出的第一个元素(或满足条件的第一个元素)。如果序列中有多个满足条件的或者不存在满足条件的,则抛出一个错误。

Observable.of("?", "?", "?", "?", "?", "?")
    .single()
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

增加判断条件

Observable.of("?", "?", "?", "?", "?", "?")
    .single { $0 == "?" }
    .subscribe { print($0) }
    .disposed(by: disposeBag)   //  next(?)   completed

Observable.of("?", "?", "?", "?", "?", "?")
    .single { $0 == "?" }
    .subscribe { print($0) }
    .disposed(by: disposeBag)     // next(?) error(Sequence contains more than one element.)

Observable.of("?", "?", "?", "?", "?", "?")
    .single { $0 == "?" }
    .subscribe { print($0) }
    .disposed(by: disposeBag)  //  error(Sequence doesn't contain any elements.)
  • take

从序列的开头获取指定数量的信号

Observable.of("?", "?", "?", "?", "?", "?")
    .take(3)
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)  //     ???

在这里插入图片描述

  • takeLast

从序列的结尾获取指定数量的元素。

Observable.of("?", "?", "?", "?", "?", "?")
    .takeLast(3)
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)  //   ???

在这里插入图片描述

  • takeWhile

在指定的条件满足时,则从可观察序列的开头发出元素。

Observable.of(1, 2, 3, 4, 5, 6)
    .takeWhile { $0 < 4 }
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)  // 1 2 3

在这里插入图片描述

  • takeUntil

从源序列中获取元素,直到引用可观察序列发出元素结束

let sourceSequence = PublishSubject<String>()
let referenceSequence = PublishSubject<String>()

sourceSequence
    .takeUntil(referenceSequence)
    .subscribe { print($0) }
    .disposed(by: disposeBag)

sourceSequence.onNext("?")
sourceSequence.onNext("?")
sourceSequence.onNext("?")

referenceSequence.onNext("?")

sourceSequence.onNext("?")
sourceSequence.onNext("?")
sourceSequence.onNext("?")   // next(?)  next(?)  next(?)  completed

在这里插入图片描述

  • skip

从序列的开头跳过指定数量的元素。

Observable.of("?", "?", "?", "?", "?", "?")
    .skip(2)
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)  // ????

在这里插入图片描述

  • skipWhile

从序列的开头跳过满足条件的元素。

Observable.of(1, 2, 3, 4, 5, 6)
    .skipWhile { $0 < 4 }
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)   // 4 5 6

在这里插入图片描述

  • skipUntil

从源序列中跳过元素,直到引用可观察序列发出元素。

let sourceSequence = PublishSubject<String>()
let referenceSequence = PublishSubject<String>()

sourceSequence
    .skipUntil(referenceSequence)
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

sourceSequence.onNext("?")
sourceSequence.onNext("?")
sourceSequence.onNext("?")

referenceSequence.onNext("?")

sourceSequence.onNext("?")
sourceSequence.onNext("?")
sourceSequence.onNext("?")  // ???

在这里插入图片描述

MVVM-C

参见下一篇文章

https://blog.csdn.net/wf96390/article/details/88370363

2019-08-16 11:18:18 qiangshuting 阅读数 1282

用OC开发的时候用的MVC架构模式,所以想在Swift中试试MVVM架构模式

都说MVVM是MVC改进而来,因为MVC的Controller太庞大了,所以给他减减重,就新建了一种ViewModel类,它继承于NSObject, 从ViewController里抽出UI逻辑,就放在了ViewModel里了,在ViewModel里每一个View里的UI控件都有与之对应的属性。
也就是说,对于在view上要展现的控件,你需要在ViewModel里设置控件相对应的数据属性,然后在controller里直接用viewModel的数据属性赋值给相应控件。
比如label的话就在viewModel里设置label.text的NSString数据属性

在这里插入图片描述

  • View层:视图展示。包含UIView以及UIViewController,View层是可以持有ViewModel的。

  • ViewModel层:视图适配器。暴露属性与View元素显示内容或者元素状态一一对应。一般情况下ViewModel暴露的属性建议是readOnly的。还有一点,ViewModel层是可以持有Model的。

  • Model层:数据模型与持久化抽象模型。数据模型很好理解,就是从服务器拉回来的JSON数据。而持久化抽象模型暂时放在Model层,是因为MVVM诞生之初就没有对这块进行很细致的描述。按照经验,我们通常把数据库、文件操作封装成Model,并对外提供操作接口。(有些公司把数据存取操作单拎出来一层,称之为DataAdapter层,所以在业内会有很多MVVM的变种,但其本质上都是MVVM)。

  • Binder:MVVM的灵魂。可惜在MVVM这几个英文单词中并没有它的一席之地,它的最主要作用是在View和ViewModel之间做了双向数据绑定。如果MVVM没有Binder,那么它与MVC的差异不是很大。

我们发现,正是因为View、ViewModel以及Model间的清晰的持有关系,所以在三个模块间的数据流转有了很好的控制。
在这里插入图片描述
上面提到了数据绑定,那么绑定到底是什么呢

绑定这个概念源于 OS X 平台的开发,但是在 iOS 平台上面,我们并没有对应的开发工具。当然,我们也有 KVO 和
通知,但是用这些方式去做绑定不太方便。

那么,如果我们不想自己去写他们的话,下面提供了两个选择:

选一个基于 KVO 的绑定库,比如 RZDataBinding 或者 SwiftBond。 使用全量级的 函数式响应编程 框架,比如
ReactiveCocoa、RxSwift 或者 PromiseKit。 实际上,现在提到「MVVM」你应该就会想到
ReactiveCocoa,反过来也是一样。虽然我们可以通过简单的绑定来实现 MVVM 模式,但是
ReactiveCocoa(或者同类型的框架)会让你更大限度的去理解 MVVM。

import UIKit

struct Person { // Model
    let firstName: String
    let lastName: String
}

protocol GreetingViewModelProtocol: class {
    var greeting: String? { get }
    var greetingDidChange: ((GreetingViewModelProtocol) -> ())? { get set } // function to call when greeting did change
    init(person: Person)
    func showGreeting()
}

class GreetingViewModel : GreetingViewModelProtocol {
    let person: Person
    var greeting: String? {
        didSet {
            self.greetingDidChange?(self)
        }
    }
    var greetingDidChange: ((GreetingViewModelProtocol) -> ())?
    required init(person: Person) {
        self.person = person
    }
    func showGreeting() {
        self.greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName
    }
}

class GreetingViewController : UIViewController {
    var viewModel: GreetingViewModelProtocol! {
        didSet {
            self.viewModel.greetingDidChange = { [unowned self] viewModel in
                self.greetingLabel.text = viewModel.greeting
            }
        }
    }
    let showGreetingButton = UIButton()
    let greetingLabel = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.showGreetingButton.addTarget(self.viewModel, action: "showGreeting", forControlEvents: .TouchUpInside)
    }
    // layout code goes here
}
// Assembling of MVVM
let model = Person(firstName: "David", lastName: "Blaine")
let viewModel = GreetingViewModel(person: model)
let view = GreetingViewController()
view.viewModel = viewModel

这个例子没有分离View和ViewController, demo只是为了方便,少创建几个,写过MVC的都知道,最好还是把View单拎出来
View里有一个greetingLabel,ViewModel里有对应的greeting: String,然后遵守一个协议,在
在 greeting 属性的 didSet 回调里面)用 greetingDidChange 闭包函数去更新 View 的显示。

优点

  • MVVM 可以兼容你当下使用的 MVC 架构。
  • MVVM 增加你的应用的可测试性。
  • MVVM 配合一个绑定机制效果最好。

参考文章

https://objccn.io/issue-13-1/
https://blog.coding.net/blog/ios-architecture-patterns

2017-09-06 14:51:02 Bloody_Leaves 阅读数 740

前言

这一篇,是一个仿Github注册的表单页面。官方针对这个例子,介绍了用RxSwift实现MVVM,一个是使用Driver 序列(针对UI界面的),另一个是Observable 序列。所以可以总结的知识点如下:

  • MVVM的实现
  • Observable序列和Driver序列的区别

功能说明

还是老样子,先说明这个例子做了什么。

这里写图片描述

其实就是一个很经典的注册表单界面:

  • 输入用户名,检验用户名是否存在
  • 输入密码,检验密码长度
  • 输入确认密码,检验与上次输入的密码是否一致
  • 注册按钮,当所有输入内容合法时,才能点击

MVVM的优点

以下摘自百度百科
MVVM模式和MVC模式一样,主要目的是分离视图(View)和模型(Model),有几大优点

  • 低耦合。视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的”View”上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
  • 可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。
  • 独立开发。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计,使用Expression Blend可以很容易设计界面并生成xml代码。
  • 可测试。界面素来是比较难于测试的,而现在测试可以针对ViewModel来写。

Driver

Driver是RxSwift专门用于处理UI流的,相比Observable,它有以下特点:

  • 不会发出Error
  • 在主线程执行
  • 自动添加 shareReplayLatestWhileConnected()

Observable和Driver之间的转换

Observable和Driver的用法基本一致,以下是一个转化例子

使用Observable:

validateUsername = input.username
         .flatMapLatest { username in
         return validationService.validateUsername(username)
         .observeOn(MainScheduler.instance)
         .catchErrorJustReturn(.failed(message: "Error contacting server"))
         }
         .shareReplay(1)

使用Driver:

validateUsername = input.username
            .flatMapLatest { username in
                return validationService.validateUsername(username)
                .asDriver(onErrorJustReturn: .failed(message: "Error contacting server"))
        }

使用 ‘Driver’的话,会自动地帮我们添加shareReplay(1),并且

.observeOn(MainScheduler.instance)
.catchErrorJustReturn(.Failed(message: “Error contacting server”))

等语句,可以简洁的写为

.asDriver(onErrorJustReturn: .Failed(message: “Error contacting server”))

bindTo和driver

bindTo和driver的作用是一样的,都是绑定结果,可以直接这样理解:

  • bindTo用于Observable
  • driver用于Driver

使用bindTo,这里的viewModel.validateUsername是Observable类型

viewModel.validateUsername
        .bind(to: usernameValidationOutlet.rx.validationResult)
        .disposed(by: disposeBag)

使用driver,这里的viewModel.validateUsername是Driver类型

 viewModel.validateUsername
            .drive(usernameValidationOutlet.rx.validationResult)
            .disposed(by: disposeBag)

总结

具体的代码就不分析了,其实就跟之前几个例子是一样的。这个例子精华在于MVVM的思想,通过ViewModel将视图和模型绑定在一起,从而达到解耦的效果。

Demo地址

https://github.com/maple1994/RxSwfitTest

ios 浅析MVVM模式

阅读数 9080