今天 先分享下MVVM
上图描述了MVVM一个基本结构,看到了什么,是不是发现比MVC架构中多了一个ViewModel,没错,就是这个ViewModel,他是MVVM相对于MVC改进的核心思想。在开发过程中,由于需求的变更或添加,项目的复杂度越来越高,代码量越来越大,此时我们会发现MVC维护起来有些吃力,首先被人吐槽的最多的就是MVC的简写变成了Massive-View-Controller(意为沉重的Controller)
由于Controller主要用来处理各种逻辑和数据转化,复杂业务逻辑界面的Controller非常庞大,维护困难,所以有人想到把Controller的数据和逻辑处理部分从中抽离出来,用一个专门的对象去管理,这个对象就是ViewModel,是Model和Controller之间的一座桥梁。当人们去尝试这种方式时,发现Controller中的代码变得非常少,变得易于测试和维护,只需要Controller和ViewModel做数据绑定即可,这也就催生了MVVM的热潮。
MVVM值得用么?
个人非常推荐使用,并且可以直接在你现有的MVC基础上进行扩展,我们首先来看下优缺点
优点:
1.Controller清晰简洁: ViewModel分离了大部分Controller代码,更加清晰和容易维护。
2.方便测试:开发中大部分Bug来至于逻辑处理,由于ViewModel分离了许多逻辑,可以对ViewModel构造单元测试。
3.开发解耦(举两个例子):
a.一人负责逻辑实现、另一人负责UI实现;
b.敏捷开发时,会发经常发不是等后端做好了接口我们再去开发,不过在没有接口的情况下通常我们可以把Controller和View完成。
缺点:
1.看起来代码会比MVC多点
2.需要对每个Controller实现绑定,如果处理不好,反而会有一种“画虎不成反类犬”的感觉
总结
在我实际使用过程中,MVVM写出的代码量并不比MVC的少,有时反而还会多点,毕竟多了一个数据绑定过程,但逻辑会清晰很多,对于多人开发的团队,还是有不少优势的,缺点和优点相比不值一提,总之推荐使用
[图片上传中...(image.png-7c4251-1600679073207-0)]
接下来举个例子
定义一个ViewModel
/// 可以不继承 : NSObject 使这个 JFGameViewModel 更轻量级
class JFGameViewModel{
//懒加载 定义一个模型数组 用来保存数据 给控制器 或者外部使用
lazy var gameModels:[JFGameModel] = [JFGameModel]()
}
extension JFGameViewModel{
func requestGameData( finishCallBack:@escaping ()->()){
// let parameters = ["shortName" : "game"]
JFNetworkTool.requestData(type: .GET, urlString: "http://capi.douyucdn.cn/api/v1/getColumnDetail") { (response) in
guard let response = response as? [String:Any] else { return }
// as? [String:NSObject] 转成数组 并且数组是字典类型
guard let dataArray = response["data"] as? [[String:Any]] else { return}
for dict in dataArray{
self.gameModels.append(JFGameModel(dict: dict))
}
finishCallBack()
}
}
}
在这个viewmodel里面做网络请求,并懒加载一个模型数据暴露给外界(大部分情况是要给Controller持有的)
在控制器中懒加载一个ViewModel 并持有这个ViewModel
fileprivate lazy var gameViewModel:JFGameViewModel = JFGameViewModel()
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return gameViewModel.gameModels.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: KGameCellID, for: indexPath) as! JFCollectionGameCell
let gameModel = gameViewModel.gameModels[indexPath.item]
// cell.group = gameModel
// Cannot assign value of type 'JFGameModel' to type 'AnchorGroup' 模型不匹配
cell.baseGame = gameModel
return cell
}
使用viewModel中的数据 这样VC里面所有的数据都会由ViewModel去请求和加工 使VC更加清爽
fileprivate lazy var topHeaderView:JFCollectionHeaderView = {
let topView = JFCollectionHeaderView.collectionHeaderView()
topView.frame = CGRect(x: 0, y: -(KHeaderViewH + KGameViewH), width: kScreenWidth, height: KHeaderViewH)
topView.titleLabel.text = "常用"
topView.iconImageView.image = UIImage(named: "Img_orange")
topView.moreBtn.isHidden = true
return topView
}()
懒加载头部的视图
override func setupUI(){
contentView = collectionView
super.setupUI()
view.addSubview(collectionView)
collectionView.addSubview(topHeaderView)
collectionView.addSubview(gameView)
collectionView.contentInset = UIEdgeInsets(top: KHeaderViewH + KGameViewH, left: 0, bottom: 0, right: 0)
}
headerView添加在collectionView上
因为头部视图要随着collectionView的滚动而滚动
collectionView.contentInset = UIEdgeInsets(top: KHeaderViewH + KGameViewH, left: 0, bottom: 0, right: 0)
目的是计算好headerView的大小
在collectionView里面的代理方法和数据源方法进行 设置数据和UI
extension GameViewController:UICollectionViewDataSource{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return gameViewModel.gameModels.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: KGameCellID, for: indexPath) as! JFCollectionGameCell
let gameModel = gameViewModel.gameModels[indexPath.item]
// cell.group = gameModel
// Cannot assign value of type 'JFGameModel' to type 'AnchorGroup' 模型不匹配
cell.baseGame = gameModel
return cell
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: KHeaderViewID, for: indexPath) as! JFCollectionHeaderView
// let group = recommendVM.anchorGroups[indexPath.section]
// headerView.anchorGroup = group
headerView.titleLabel.text = "全部"
headerView.iconImageView.image = UIImage(named: "Img_orange")
headerView.moreBtn.isHidden = true
return headerView
}
}
这些基本上和OC的那套差不多
抽取基类
class BaseViewController: UIViewController {
var contentView:UIView?
fileprivate lazy var imageView:UIImageView = { [unowned self] in
let imageView = UIImageView(image: UIImage(named: "img_loading_1"))
imageView.center = self.view.center
//数组中不能试可选类型
imageView.animationImages = [UIImage(named: "img_loading_1")!,UIImage(named: "img_loading_2")!]
imageView.animationDuration = 0.5
//LONG_MAX 非常大的整形
imageView.animationRepeatCount = LONG_MAX
//顶部和底部随父控件的 拉伸而拉伸
imageView.autoresizingMask = [.flexibleTopMargin,.flexibleBottomMargin]
return imageView
}()
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
func setupUI(){
view.backgroundColor = UIColor.white
contentView?.isHidden = true
view.addSubview(imageView)
imageView.startAnimating()
}
func loadDataFinished(){
imageView.stopAnimating()
imageView.isHidden = true
contentView?.isHidden = false
}
}
1、暴露一个contentView给外部 填充
2、loadDataFinished 暴露数据加载完成 刷新UI
好了 接下来
归纳下Swift一些常见且高频注意的点:
枚举类型
结构体
改变成员的属性,如果在函数红星改变成员的属性,那么该函数前必须加上mutating
加下滑"_" 代表参数可以 label 可以省略
类的基本使用
类里面定义属性 必须要对属性进行初始化
创建一个类用()创建 其实就是在调用构造函数
为什么 在类里面定义一个属性 必须要 对这个属性进行初始化
答案:应为类初始化的时候 会自动调用 初始化构建函数 而在这个是初话构建函数里面 会要求 里面的每一个属性进行初始化
好了今天就和大家分享到这
附上源码
源码
如果有啥问题希望大家一起纠正,非常感谢。
或者大家有想要的分享方式。我后续也会开始准备。
再见。