【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 tableView
    self.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 helper
    NSArray *data = @[@"Item 1", @"Item 2", @"Item 3"]; // Data for the tableView
    self.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 viewModel
    UserModel *user = [[UserModel alloc] init];
    user.name = @"John";
    user.age = 30;
    self.viewModel = [[UserViewModel alloc] initWithUser:user];
    
    // Setup UI
    self.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 被快速传递到别的位置,要定位原始出问题的地方就变得不那么容易了。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

打造清洁宜居家园保护自然生态环境,基于YOLOv7【tiny/l/x】参数系列模型开发构建自然生态场景下违规违法垃圾倾倒检测识别系统

自然生态环境&#xff0c;作为我们人类赖以生存的家园&#xff0c;其健康与否直接关系到我们的生活质量。然而&#xff0c;近年来&#xff0c;一些不法分子为了个人私利&#xff0c;在河边、路边等公共区域肆意倾倒垃圾&#xff0c;严重破坏了环境的健康与平衡。这种行为不仅损…

语音识别-paddlespeech-流程梳理

上一次研究语音识别是21年年底的事情了&#xff0c;记得当时是先进行了语音识别的应用&#xff0c;然后操作了模型的再次训练&#xff1b;两年过去&#xff0c;关于ASR相关流程忘得差不多了&#xff0c;这次基于paddlespeech的代码&#xff0c;进行了流程的梳理&#xff0c;关于…

【cpp】并发多线程 Unique

1. unique_lock 何时锁定资源。 unique_lock lock1 时候&#xff0c;还没有锁住资源。 实际是后面&#xff0c;显式的出发&#xff1a; 比如&#xff0c; lock.lock, 或 std::lock(lk1,lk2), 或者条件变量CV.wait(mtx, []{!re})。 #include <iostream> #include <mu…

HIVE大数据平台SQL优化分享

相信很多小伙伴在面试的时候,必然跳不过去的一个问题就是SQL脚本的优化,这是很多面试官爱问的问题,也是可以证明你实力进阶的一个重要的能力。 下面给大家分享一个重量级的大数据行业sql技能---hive大数据平台SQL优化。 此文章是大数据平台运维组从多维度参数(CPU,内存,…

vwmare虚拟机迁移磁盘方法

前言 欢迎来到我的博客 个人主页:北岭敲键盘的荒漠猫-CSDN博客 本文整理 虚拟机迁移磁盘的方法 简单方便快上手 当前目标 当前迁移文件: 当前位置&#xff1a; 目的地: e盘虚拟机文件夹 迁移到当前目录。 实际操作 先打开虚拟机的设置&#xff0c;找到这个虚拟机当前的位置…

苹果cms:伪静态设置教程

官方默认的网站模式是动态模式&#xff0c;动态模式下链接中自带有“index.php”想要去除网站链接中的index.php&#xff0c;首先需要开启网站的模式为伪静态模式。这样比动态模式那一长串的链接看着也舒服一些&#xff0c;最重要的是迎合搜索引擎的喜好加快收录提高排名。 1、…

HIVE解决连续登录问题

HIVE解决连续登录问题 目录 HIVE解决连续登录问题 1.解决连续登录问题 如何去分析数据&#xff1a; 2.需求&#xff1a; 3.-- 间隔天数 1.解决连续登录问题 如何去分析数据&#xff1a; 1&#xff09;查看数据的字段信息 …

Java进阶-SpringCloud设计模式-工厂模式的设计与详解

一、设计模式介绍 设计模式是我们开发中常常需要面对的核心概念&#xff0c;它们是解决特定问题的模板或者说是经验的总结。这些模式被设计出来是为了让软件设计更加清晰、代码更加可维护且能应对未来的变化。良好的设计模式不仅能解决重复代码的问题&#xff0c;还能使团队中…

计算机毕业设计 | SpringBoot健身房管理系统(附源码)

1&#xff0c;项目背景 随着人们生活水平的提高和健康意识的增强&#xff0c;健身行业逐渐兴起并迅速发展。而现代化的健身房管理系统已经成为健身房发展的必备工具之一。传统的健身房管理方式已经无法满足现代化健身房的需求&#xff0c;需要一种更加高效、智能、安全的管理系…

在云计算与人工智能中,7ECloud扮演着什么样的角色

数据驱动的时代&#xff0c;云计算和人工智能已成为推动现代科技进步的两大引擎。作为一家专注于云计算的公司&#xff0c;7ECloud正是在这个领域发挥自己的力量&#xff0c;力图为企业提供一站式解决方案&#xff0c;并拥有来自厂家的源头支持&#xff0c;用极其低的价格助力企…

【51】Camunda8-Zeebe核心引擎-Zeebe Gateway

概述 Zeebe网关是Zeebe集群的一个组件,它可以被视为Zeebe集群的联系点,它允许Zeebe客户端与Zeebe集群内的Zeebe代理进行通信。有关Zeebe broker的更多信息,请访问我们的附加文档。 总而言之,Zeebe broker是Zeebe集群的主要部分,它完成所有繁重的工作,如处理、复制、导出…

消息中间件是什么?有什么用?常见的消息中间件有哪些?

1.什么是消息中间件&#xff1f; 消息中间件基于队列模型在网络环境中为应用系统提供同步或异步、可靠的消息传输的支撑性软件系统。 2.现实中的痛点&#xff1a; 1.Http请求基于请求与响应的模型&#xff0c;在高并发的情况下&#xff0c;客户端发送大量的请求达到服务器端…

[华为OD]BFS C卷 200 智能驾驶

题目&#xff1a; 有一辆汽车需要从m*n的地图的左上角(起点)开往地图的右下角(终点)&#xff0c;去往每一个地区都需 要消耗一定的油量&#xff0c;加油站可进行加油 请你计算汽车确保从起点到达终点时所需的最少初始油量说明&#xff1a; (1)智能汽车可以上下左右四个方向…

PyQt5中的QGraphicsView()

文章目录 1. 简介2. 一个简单的示例2. 加载一幅图片3. 常用方法示例 1. 简介 QGraphicsView是PyQt5中用于显示图形场景的小部件&#xff0c;它提供了许多常用的方法来控制视图的行为和属性。下面是一些常用的QGraphicsView方法&#xff1a; setScene(scene): 设置要显示的场景…

GCP谷歌云有什么数据库类型,该怎么选择

GCP谷歌云提供的数据库类型主要包括&#xff1a; 关系型数据库&#xff1a;这类数据库适用于结构化数据&#xff0c;通常用于数据结构不经常发生变化的场合。在GCP中&#xff0c;关系型数据库选项包括Cloud SQL和Cloud Spanner。Cloud SQL提供托管的MySQL、PostgreSQL和SQL Se…

Office之Word应用(二)

一、页眉添加文件名称和页码 1、双击页眉&#xff0c;点击“页眉-空白&#xff08;三栏&#xff09;” 2、删掉第一处&#xff08;鼠标放在上面就会选中&#xff0c;Enter即可&#xff09;&#xff0c;第二处输入文档名称&#xff0c;第三处插入页码。 注&#xff1a;插入页码时…

微信小程序毕业设计-基于Java后端的微信小程序源码150套(附源码+数据库+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f9e1;今天给大家分享150的微信小程序毕业设计&#xff0c;后台用Java开发&#xff0c;这些项目都经过精心挑选&#xff0c;涵盖了不同的实战主题和用例&#xff0c;可做毕业设…

灾备建设中虚拟机备份自定义数据块大小应用

灾备建设中&#xff0c;传输备份数据时&#xff0c;自定义数据块大小可以帮助优化数据传输和存储效率。 确定数据块大小&#xff0c;首先&#xff0c;需要确定合适的数据块大小。这可以根据备份数据量和网络带宽来决定。通常情况下&#xff0c;较小的数据块可以更好地适应网络…

82.网络游戏逆向分析与漏洞攻防-移动系统分析-坐标修正数据包的处理与模拟

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果&#xff0c;代码看不懂是正常的&#xff0c;只要会抄就行&#xff0c;抄着抄着就能懂了 内容…

采油厂职工向媒体投稿的好方法找到了

作为一名采油厂的职工,我深知在媒体上定期投稿的重要性。这不仅是我们展示工作成果、传播企业文化的重要途径,更是上级考核我们工作表现的一项指标。然而,在投稿的过程中,我经历了不少心酸与困扰。 起初,我采用传统的邮箱投稿方式。每天,我都会花费大量时间在网络上搜索合适的媒…