【iOS】架构模式

文章目录

  • 前言
  • 一、MVC
  • 二、MVP
  • 三、MVVM


前言

之前写项目一直用的是MVC架构,现在来学一下MVP与MVVM两种架构,当然还有VIPER架构,如果有时间后面会单独学习

一、MVC

在这里插入图片描述

MVC架构先前已经详细讲述,这里不再赘述,我们主要讲一下MVC的优化【iOS】MVC

众所周知,MVC最大的问题是我们的C层十分臃肿,因为所有的事件处理逻辑都写在了C层

我们分几步来解决这个问题:

  • 业务逻辑分离:将业务逻辑移出视图控制器,放入单独的模型或管理类中。例如,数据处理、网络请求和数据转换等应该在模型或专门的业务逻辑类中处理

例如我们可以单独抽象出来一个单例Manager类负责网络请求在这里插入图片描述

  • 委托和数据源分离:尽量将UITableViewUICollectionViewdataSourcedelegate方法移到其他类中,比如创建专门的类来处理这些逻辑。

例如可以单独抽象出一个类负责协议处理

TableViewDataSourceAndDelegate.h

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>@interface TableViewDataSourceAndDelegate : NSObject <UITableViewDataSource, UITableViewDelegate>@property (strong, nonatomic) NSArray *data;- (instancetype)initWithData:(NSArray *)data;@end

TableViewDataSourceAndDelegate.m

#import "TableViewDataSourceAndDelegate.h"@implementation TableViewDataSourceAndDelegate- (instancetype)initWithData:(NSArray *)data {self = [super init];if (self) {_data = data;}return self;
}- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {return self.data.count;
}- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {static NSString *cellIdentifier = @"Cell";UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];cell.textLabel.text = self.data[indexPath.row];return cell;
}// Implement other delegate methods as needed
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {NSLog(@"Selected row at index path: %@", indexPath);
}@end

使用这个类

ViewController.m

#import "ViewController.h"@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// Initialize the tableViewself.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"Cell"];[self.view addSubview:self.tableView];// Initialize and set the tableView helperNSArray *data = @[@"Item 1", @"Item 2", @"Item 3"]; // Data for the tableViewself.tableViewHelper = [[TableViewDataSourceAndDelegate alloc] initWithData:data];self.tableView.dataSource = self.tableViewHelper;self.tableView.delegate = self.tableViewHelper;
}
@end

二、MVP

所谓设计模式,就是设计过程中为了解决普遍性问题提出的方案,我们当前的问题就是MVC的C层十分臃肿,为了解决这个问题我们提出了MVP
在这里插入图片描述
看上去与MVC相似,但是实际上表示的意义是
View层持有Presenter层,Presenter层持有Model层,View层并不可直接访问到Model层

其本质就是我们抽象出一个Presenter层去处理用户操作以及更新UI的逻辑,以减少V层的代码量,现在的V层就是View+ViewController层

首先我们需要定义一个PresenterDelegate来抽象出一些UI交互的方法,例如点击按钮更新UI或是数据

@protocol PresenterProtocol <NSObject>@required@optional
-(void)didClickAddBtnWithNum:(int)num indexPath:(NSIndexPath *)indexPath;
-(void)reloadUI;
@end

然后我们即可以在V层去实现协议中的方法,也可以在P层去实现方法,当然也可以将方法定义在P层去实现,例如
在这里插入图片描述

V层发生的变化通知P层后,由P层去处理这些变化,处理完毕后再回调给V层更新UI,同时更新M层中的数据

例如这段P层代码中这个方法

//P层
-(void)didClickAddBtnWithNum:(int)num indexPath:(NSIndexPath *)indexPath {@synchronized (self) {// 处理数据if (indexPath.row < self.dataArray.count) {UserInfoModel *model = self.dataArray[indexPath.row];model.num = [NSString stringWithFormat:@"%d",num];}if (num == 100) {UserInfoModel *model = self.dataArray[indexPath.row];[self.dataArray removeAllObjects];[self.dataArray addObject:model];//处理完毕后进行回调if(self.delegate && [self.delegate respondsToSelector:@selector(reloadUI)]) {[self.delegate reloadUI];}}}
}//V层
//在这里需要更新Model层数据, 通过中介presenter来把数据变化信息传递给Model层
-(void)setNum:(int)num {_num = num;self.numLabel.text = [NSString stringWithFormat:@"%d",num];if ((self.delegate) && [self.delegate respondsToSelector:@selector(didClickAddBtnWithNum:indexPath:)]) {[self.delegate didClickAddBtnWithNum:num indexPath:self.indexPath];}
}

可以看到现在的P层就是把原来V层的代码单独抽象出一个类,就像我们之前的网络请求单例类,然后让V层持有这个类,需要更新的时候调用P层中的方法,然后P层回调更新UI

在这里插入图片描述

同时P层因为只包含逻辑,所以更好进行测试,也就是使用断点等工具
在这里插入图片描述

MVP优缺点

  • UI布局和数据逻辑代码划分界限更明确。
  • 理解难度尚可,较容易推广。
  • 解决了Controller的臃肿问题
  • Presenter-Model层可以进行单元测试
  • 需要额外写大量接口定义和逻辑代码(或者自己实现KVO监视)。

在MVP中,ViewPresenter之间通常是双向的。View通过接口将用户操作传递给PresenterPresenter处理完毕后,再通过接口更新View

三、MVVM

随着UI交互的复杂,MVP的缺点也暴露了出来,就是会出现十分多的借口,同时每一次更新P层都会进行回调,同时回调需要细细处理

此时P层也会变得十分臃肿,这个时候就出现了MVVM

Model:同样负责应用的数据和业务逻辑。
View负责展示界面,不处理任何逻辑,只负责将用户操作传递给ViewModel
ViewModel:作为数据转换器,负责逻辑和数据的转换,以便数据可以方便地显示在View上。它反映了特定View的数据状态
在这里插入图片描述
这张图的基本逻辑还是没变,就是将我们的P层改成了ViewModel

首先ViewModel-Model层和之前的Present-Model层一样,没有什么大的变化。View持有ViewModel,这个和MVP也一样。变化主要在两个方面:

  • MVP在处理逻辑时并不会存储数据,但是MVVM中的ViewModel会对处理的数据进行一个存储
  • View与ViewModel实现了数据绑定View不需要传递操作来控制ViewModel,同时ViewModel也不会直接回调来修改View

MVVM的亮点在于:

ViewViewModel之间主要通过数据绑定(Data
Binding)进行通信。ViewModel不直接引用View,任何状态的改变都通过绑定机制自动更新到View上,这减少了大量的胶水代码。
甚至有很多人觉得应该称MVVM为MVB(Model-View-Binder)。

我们在这里多次提到了数据绑定,那么在iOS中我们使用什么来实现数据绑定呢
这里有两种方式,一种是RAC编程,后面会专门讲
我们来讲一下用KVO实现数据绑定

示例:简单的用户界面和用户数据交互
我们将构建一个小应用,显示一个用户的名字和年龄,并允许通过界面更新名字。

  • Model

模型层保持简单,只包含基本的用户数据

// UserModel.h
#import <Foundation/Foundation.h>@interface UserModel : NSObject@property (strong, nonatomic) NSString *firstName;
@property (strong, nonatomic) NSString *lastName;
@property (assign, nonatomic) NSInteger age;- (instancetype)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName age:(NSInteger)age;@end// UserModel.m
#import "UserModel.h"@implementation UserModel- (instancetype)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName age:(NSInteger)age {self = [super init];if (self) {_firstName = firstName;_lastName = lastName;_age = age;}return self;
}@end
  • 创建ViewModel

ViewModel将使用KVO来通知视图关于数据变化。

// UserViewModel.h
#import <Foundation/Foundation.h>
#import "UserModel.h"@interface UserViewModel : NSObject
@property (strong, nonatomic) UserModel *user;
@property (strong, nonatomic, readonly) NSString *userInfo;
- (instancetype)initWithUser:(UserModel *)user;
@end// UserViewModel.m
#import "UserViewModel.h"@implementation UserViewModel- (instancetype)initWithUser:(UserModel *)user {if (self = [super init]) {_user = user;[self.user addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];[self.user addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];}return self;
}- (void)dealloc {[self.user removeObserver:self forKeyPath:@"name"];[self.user removeObserver:self forKeyPath:@"age"];
}- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {if ([keyPath isEqualToString:@"name"] || [keyPath isEqualToString:@"age"]) {[self updateUserInfo];}
}- (void)updateUserInfo {self->_userInfo = [NSString stringWithFormat:@"%@, %ld years old", self.user.name, (long)self.user.age];
}@end
  • 配置ViewController

在视图控制器中,我们将使用ViewModel,并观察ViewModeluserInfo属性

// ViewController.m
#import "ViewController.h"
#import "UserViewModel.h"
#import "UserModel.h"@interface ViewController ()
@property (strong, nonatomic) UserViewModel *viewModel;
@property (strong, nonatomic) UILabel *userInfoLabel;
@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// Setup user and viewModelUserModel *user = [[UserModel alloc] init];user.name = @"John";user.age = 30;self.viewModel = [[UserViewModel alloc] initWithUser:user];// Setup UIself.userInfoLabel = [[UILabel alloc] initWithFrame:CGRectMake(20, 100, 300, 20)];[self.view addSubview:self.userInfoLabel];// Bind ViewModel to the label[self.viewModel addObserver:self forKeyPath:@"userInfo" options:NSKeyValueObservingOptionNew context:nil];[self updateUserInfoDisplay];
}- (void)dealloc {[self.viewModel removeObserver:self forKeyPath:@"userInfo"];
}- (void)updateUserInfoDisplay {self.userInfoLabel.text = self.viewModel.userInfo;
}- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {if ([keyPath isEqualToString:@"userInfo"]) {[self updateUserInfoDisplay];}
}@end

这段代码中就是ViewModel监听Model,View监听ViewModel从而实现自动更新变化,避免了重复的接口定义以及回调

上面的代码时ViewModel->View的绑定,那么如何实现View->ViewModel呢,接下来也有一个例子

  • ViewModel
// UserViewModel.h
#import <Foundation/Foundation.h>
#import "UserModel.h"@interface UserViewModel : NSObject
@property (strong, nonatomic) UserModel *user;
- (instancetype)initWithUser:(UserModel *)user;
- (void)updateUsername:(NSString *)username;
- (void)updatePassword:(NSString *)password;
@end// UserViewModel.m
#import "UserViewModel.h"@implementation UserViewModel- (instancetype)initWithUser:(UserModel *)user {if (self = [super init]) {_user = user;}return self;
}- (void)updateUsername:(NSString *)username {self.user.username = username;
}- (void)updatePassword:(NSString *)password {self.user.password = password;
}@end
  • VC
// ViewController.h
#import <UIKit/UIKit.h>
#import "UserViewModel.h"@interface ViewController : UIViewController
@property (strong, nonatomic) UserViewModel *viewModel;
@property (strong, nonatomic) UITextField *usernameField;
@property (strong, nonatomic) UITextField *passwordField;
@end// ViewController.m
#import "ViewController.h"@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];UserModel *user = [[UserModel alloc] init];self.viewModel = [[UserViewModel alloc] initWithUser:user];self.usernameField = [[UITextField alloc] initWithFrame:CGRectMake(20, 100, 280, 40)];self.passwordField = [[UITextField alloc] initWithFrame:CGRectMake(20, 150, 280, 40)];[self.view addSubview:self.usernameField];[self.view addSubview:self.passwordField];[self.usernameField addTarget:self action:@selector(usernameDidChange:) forControlEvents:UIControlEventEditingChanged];[self.passwordField addTarget:self action:@selector(passwordDidChange:) forControlEvents:UIControlEventEditingChanged];
}- (void)usernameDidChange:(UITextField *)textField {[self.viewModel updateUsername:textField.text];
}- (void)passwordDidChange:(UITextField *)textField {[self.viewModel updatePassword:textField.text];
}@end

VC层的控件的变化会让ViewModel层的数据自动变化

MVVM 的优势

  • 低耦合:View 可以独立于Model变化和修改,一个 viewModel 可以绑定到不同的 View 上

  • 可重用性:可以把一些视图逻辑放在一个 viewModel里面,让很多 view 重用这段视图逻辑

  • 独立开发:开发人员可以专注于业务逻辑和数据的开发 viewModel,设计人员可以专注于页面设计

  • 可测试:通常界面是比较难于测试的,而 MVVM 模式可以针对 viewModel来进行测试

MVVM 的弊端

数据绑定使得Bug 很难被调试你看到界面异常了,有可能是你 View 的代码有 Bug,也可能是 Model 的代码有问题。数据绑定使得一个位置的 Bug 被快速传递到别的位置,要定位原始出问题的地方就变得不那么容易了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/11736.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Golang | Leetcode Golang题解之第87题扰乱字符串

题目&#xff1a; 题解&#xff1a; func isScramble(s1, s2 string) bool {n : len(s1)dp : make([][][]int8, n)for i : range dp {dp[i] make([][]int8, n)for j : range dp[i] {dp[i][j] make([]int8, n1)for k : range dp[i][j] {dp[i][j][k] -1}}}// 第一个字符串从 …

【SAP ABAP学习资料】通过RFC接口上传图片至SAP 图片格式转换 图片大小调整

SAP图片相关&#xff1a; 链接: 【SAP ABAP学习资料】图片上传SAP 链接: 【SAP ABAP学习资料】屏幕图片预览 链接: 【SAP ABAP学习资料】smartforms打印图片&#xff0c;动态打印图片 需求&#xff1a; SAP上传图片只能本地电脑选择图片通过SE78或PERFORM IMPORT_BITMAP_BDS上…

Milvus入门初探

引言 Milvus 是一款开源的向量数据库&#xff0c;专为处理向量搜索任务而设计。它支持多种类型的向量&#xff0c;如浮点向量、二进制向量等&#xff0c;并且可以处理大规模的向量数据。Milvus 在 AI 应用中非常流行&#xff0c;尤其是在需要执行相似性搜索或最近邻搜索的场景…

【超详细】跑通YOLOv8之深度学习环境配置3-YOLOv8安装

环境配置3下载安装内容如下&#xff1a; 1、配置清华等镜像源 2、创建环境 3、下载安装Pytorch 4、下载安装YOLOv8运行环境 版本&#xff1a;Python3.8&#xff08;要求>3.8&#xff09;&#xff0c;torch1.12.0cu113&#xff08;要求>1.8&#xff09; 1、配置清华等镜…

算法-卡尔曼滤波之为什么要使用卡尔曼滤波器

假设使用雷达来预测飞行器的位置&#xff1b; 预先的假设条件条件: 1.激光雷达的激光束每5s发射一次&#xff1b; 2.通过接受的激光束&#xff0c;雷达估计目标当前时刻的位置和速度&#xff1b; 3.同时雷达要预测下一时刻的位置和速度 根据速度&#xff0c;加速度和位移的…

ESP32重要库示例详解(三):按键之avdweb_Switch库

在Arduino开发中&#xff0c;我们经常需要处理按钮和开关的输入。avdweb_Switch库就是为了简化这一任务&#xff0c;提供了一个优雅且高效的事件处理方式。本文将通过一个实际示例&#xff0c;介绍该库的主要特性和用法。 导入库 在Arduino IDE导入avdweb_Switch库的步骤如下…

Python---NumPy万字总结【此篇文章内容难度较大,线性代数模块】(3)

NumPy的应用&#xff08;3&#xff09; 向量 向量&#xff08;vector&#xff09;也叫矢量&#xff0c;是一个同时具有大小和方向&#xff0c;且满足平行四边形法则的几何对象。与向量相对的概念叫标量或数量&#xff0c;标量只有大小&#xff0c;绝大多数情况下没有方向。我们…

家居分类的添加、修改、逻辑删除和批量删除

文章目录 1.逻辑删除家居分类1.将之前的docker数据库换成云数据库2.树形控件增加添加和删除按钮1.找到控件2.粘贴四个属性到<el-tree 属性>3.粘贴两个span到<el-tree>标签里4.代码5.效果6.方法区新增两个方法处理添加和删除分类7.输出查看一下信息8.要求节点等级小…

李开复引领的零一万物开源了Yi-1.5模型,推出了6B、9B、34B三个不同规模的版本

零一万物&#xff0c;由李开复博士引领的AI 2.0公司&#xff0c;近期开源了其备受瞩目的Yi-1.5模型&#xff0c;这一举措再次彰显了公司在人工智能领域的创新实力与开放精神。Yi-1.5模型作为零一万物的重要技术成果&#xff0c;不仅代表了公司在大模型技术研发上的新高度&#…

冥想的时候怎么专注自己

冥想的时候怎么专注自己&#xff1f;我国传统的打坐养生功法&#xff0c;实际最早可追溯到五千年前的黄帝时代。   每天投资两个半小时的打坐&#xff0c;有上千年之久的功效。因为当你们打坐进入永恒时&#xff0c;时间停止了。这不只是两个半小时&#xff0c;而是百千万亿年…

为什么3d重制变换模型会变形?---模大狮模型网

3D建模和渲染过程中&#xff0c;设计师经常会遇到一个让人头疼的问题&#xff0c;那就是模型在进行重制变换后出现的意外变形。这种变形不仅影响了模型的外观和质量&#xff0c;也给设计工作带来了额外的麻烦。本文将深入探讨3D模型进行重制变换后出现变形的原因&#xff0c;帮…

回炉重造java----JVM

为什么要使用JVM ①一次编写&#xff0c;到处运行&#xff0c;jvm屏蔽字节码与底层的操作差异 ②自动内存管理&#xff0c;垃圾回收功能 ③数组下边越界检查 ④多态 JDK&#xff0c;JRE&#xff0c;JVM的关系 JVM组成部分 JVM的内存结构 《一》程序计数器(PC Register) 作用…

傻瓜化备份/恢复K8S集群Etcd数据

前言&#xff1a; 备份重要数据&#xff0c;简化重复操作&#xff0c;让一指禅、点点点也能完成运维任务。 脚本呈现界面如下&#xff1a; 1、查看Etcd版本 rootmaster:~# cat /etc/kubernetes/manifests/etcd.yaml | grep image: | awk {print $2} registry.aliyuncs.com/goo…

SpringCloud------Eureka,Ribbon,Nacos

认识微服务 微服务技术栈 微服务概念 微服务结构 微服务技术对比 企业需求 SpringCloud 认识Springcloud 服务拆分及远程调用 每个服务只能查询自己数据库中的表&#xff0c;导致其他服务如果想使用别人的表数据&#xff0c;这就需要进行远程调用&#xff0c;这里使用RestTem…

杨校老师项目之基于51单片机的汽车智能照明系统【嵌入式】

获取全套资料&#xff1a; 有偿获取&#xff1a;mryang511688 技术&#xff1a;C语言、单片机等 摘要&#xff1a; 科技的发展&#xff0c;人们对汽车的安全性也提出了更高要求。照明系统作为汽车组成部分之一&#xff0c;承担着重要职能&#xff0c;传统汽车智能照明系统已无法…

【云计算小知识】云管理的作用是什么?

云计算已经成为推动企业数字化转型&#xff0c;提升运营效率的重要力量。而在这个过程中&#xff0c;云管理作为确保云计算环境稳定、高效运行的关键环节&#xff0c;其作用愈发凸显。今天我们小编就给大家详细介绍一下云管理的作用是什么&#xff1f; 云管理的作用是什么&…

小程序的小组件

进度的组件 文字换行过滤 以及 排序 简单易懂 只为了记录工作 <template><div><ProgressBar :progress"progress" /><button click"increaseProgress">增加进度</button><view class"goods-name">12…

【408精华知识】提高外部排序速度的三种方式

文章目录 一、败者树二、置换-选择排序三、最佳归并树 一、败者树 还没写完… 二、置换-选择排序 三、最佳归并树 写在后面 这个专栏主要是我在学习408真题的过程中总结的一些笔记&#xff0c;因为我学的也很一般&#xff0c;如果有错误和不足之处&#xff0c;还望大家在评…

Wikimedia To Opensearch

概览 Wikimedia ⇒ Kafka ⇒ OpensearchJava Library&#xff1a;OKhttp3和OkHttp EventSource&#xff1b;生产者&#xff1a;Wikimedia&#xff1a;WikimediaChangeHandler和WikimediaChangeProducer&#xff1b;消费者&#xff1a;Opensearch&#xff1a;OpenSearchConsume…

AI智能体|我把Kimi接入了个人微信

大家好&#xff0c;我是无界生长。 最近加入AI学习交流群的小伙伴越来越多&#xff0c;我打算在微信群接入一个聊天机器人&#xff0c;让它协助管理微信群&#xff0c;同时也帮忙给群友解答一些问题。普通的群聊机器人肯定是不能满足需求的&#xff0c;得上AI大模型&#xff0c…