读者好!
在本文中,我将讨论iOS应用程序的体系结构
-Clean Swift 。 我们将考虑主要理论要点,并在实践中分析一个实例。

理论
首先,我们将分析体系结构的基本术语。 在
Clean Swift中,应用程序由场景组成,即 每个应用程序屏幕都是一个场景。 场景中的主要交互经过
ViewController- >
Interactor- >
Presenter组件之间的顺序循环。 这称为
VIP周期。
组件之间的桥梁是
Models文件,该文件存储传输的数据。 还有一个
Router ,负责在场景之间传输和传输数据,还有
Worker ,它接管了
Interactor逻辑的一部分。

检视
通过代码编写的情节提要,XIB或UI元素。
ViewController
仅负责
View的配置和交互。 控制器不应包含任何业务逻辑,网络交互,计算等。
它的任务是在
Interactor中使用
View处理事件,显示或发送数据(不进行处理和检查)。
牵连器
它包含场景的业务逻辑。
它与网络,数据库和设备模块一起使用。
Interactor从
ViewController接收一个请求(数据为空或为空),对其进行处理,并在必要时将新数据传输到
Presenter 。
主讲人
他从事显示数据的准备工作。
例如,在电话号码中添加掩码或在标题大写的第一个字母。
它处理从
Interactor接收到的数据,然后将其发送回
ViewController 。
型号
在
VIP周期的各个组件之间传输数据的一组结构。 循环的每个圈具有3种类型的结构:
- 请求 -数据结构(来自TextField等的文本),用于从ViewController传输到Interactor
- 响应 -具有数据结构(从网络等下载),以便从Interactor传输到Presenter
- ViewModel-具有在Presenter中处理的数据(文本格式等)的结构,用于传输回ViewController
工人
如果
Interactor迅速增长,则卸载
Interactor ,承担应用程序的部分业务逻辑。
如果在多个场景中使用了它们的功能,则还可以创建所有场景都通用的Worker。
例如,在
Worker中,您可以使使用网络或数据库的逻辑成为可能。
路由器
所有负责场景之间的过渡和数据传输的逻辑都在
Router中提取。
为了阐明
VIP周期的情况,我将举一个标准示例-授权。
- 用户输入用户名和密码,然后单击授权按钮
- ViewController触发IBAction ,然后使用输入到TextFields中的用户数据创建一个结构,(模型->请求)
- 创建的结构被传递给Interactor'e中的fetchUser方法
- Interactor向网络发送请求,并接收有关授权成功的响应
- 根据接收到的数据,使用结果创建一个结构(“模型”->“响应”),并将其传递给Presenter'e中的presentUser方法
- Presenter根据需要设置数据格式,然后将其返回(模型-> ViewModel)到ViewController'e中的displayUser方法
- ViewController将接收到的数据显示给用户。 在授权的情况下,可能会显示错误或使用路由器触发到另一个场景的转换
因此,我们得到了一个单一且一致的结构,并将职责分配到各个组件中。
练习
现在,让我们看一个小的实际示例,该示例显示
VIP周期如何进行。 在此示例中,我们将在打开场景(屏幕)时模拟数据加载。 我用注释标记了代码的主要部分。
整个
VIP周期与协议相关,协议可在不中断应用程序的情况下更换任何模块。
为
ViewController创建了
DisplayLogic协议,该链接传递给
Presenter以便以后调用。 对于
Interactor ,创建了两个
BusinessLogic协议,它们分别负责从
ViewController和
DataSource调用方法,用于存储数据并通过
Router将另一个场景传输到
Interactor 。 Presenter订阅了
PresentationLogic协议以从
Interactor进行呼叫。 所有这些的连接元素是
Models 。 它包含一些结构,借助这些结构可以在
VIP周期的各个组件之间交换信息。 我们将以此开始代码分析。

型号
在下面的示例中,对于
Home场景,我创建了一个
HomeModels文件,其中包含
VIP循环的一组查询。
FetchUser请求将负责加载用户数据,我们将进一步考虑。
| // Models |
| /// VIP |
| enum HomeModels { |
| |
| /// VIP |
| enum FetchUser { |
| |
| /// Interactor View Controller |
| struct Request { |
| let userName: String |
| } |
| |
| /// Presentor Interactor |
| struct Response { |
| let userPhone: String |
| let userEmail: String |
| } |
| |
| /// View Controller Presentor |
| struct ViewModel { |
| let userPhone: String |
| let userEmail: String |
| } |
| } |
| } |
ViewController
初始化类后,我们将实例化此场景的
Interactor和
Presenter类,并在它们之间建立依赖关系。
此外,在
ViewController'e中,仅存在指向
Interactor的链接。 使用此链接,我们将创建一个对
Interactor中的
fetchUser(request :)方法的
请求 ,以开始
VIP周期。
在这里值得注意对
Interactor的请求是如何发生的。 在
loadUserInfromation()方法中,我们创建
Request结构的实例,并在其中传递初始值。 可以从
TextField ,表等中获取。
Request结构的实例传递到
fetchUser(request :)方法,该方法位于
Interactor的
BusinessLogic协议中。
| // ViewController |
| /// |
| protocol HomeDisplayLogic: class { |
| |
| /// |
| func displayUser(_ viewModel: HomeModels.FetchUser.ViewModel) |
| } |
| |
| final class HomeViewController: UIViewController { |
| |
| /// Interactor'a |
| var interactor: HomeBusinessLogic? |
| |
| override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { |
| super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) |
| setup() |
| } |
| |
| required init?(coder aDecoder: NSCoder) { |
| super.init(coder: aDecoder) |
| setup() |
| } |
| |
| /// |
| private func setup() { |
| // VIP |
| let interactor = HomeInteractor() |
| let presenter = HomePresenter() |
| |
| // |
| interactor.presenter = presenter |
| presenter.viewController = self |
| |
| // Interactor View Controller |
| self.interactor = interactor |
| } |
| |
| override func viewDidLoad() { |
| super.viewDidLoad() |
| |
| // |
| fetchUser() |
| } |
| |
| /// Interactor |
| private func loadUserInfromation() { |
| // Interactor |
| let request = HomeModels.FetchUser.Request(userName: "Aleksey") |
| |
| // Interactor'a |
| interactor?.fetchUser(request) |
| } |
| } |
| |
| /// HomeDisplayLogic |
| extension HomeViewController: HomeDisplayLogic { |
| |
| func displayUser(_ viewModel: HomeModels.FetchUser.ViewModel) { |
| print(viewModel) |
| } |
| } |
牵连器
Interactor类的实例包含指向
PresentationLogic协议的链接,在此协议下对
Presenter进行了签名。
fetchUser(request :)方法可以包含任何数据加载逻辑。 例如,我刚刚创建了带有假定获得的数据的常量。
使用相同的方法,将创建
Response结构的实例,并使用之前获得的参数填充该实例。 使用
presentUser(response :)方法将
响应传递到
PresentationLogic 。 换句话说,这里我们获取了原始数据,并将其传递给
Presenter进行处理。
| // Interactor |
| /// Interactor'a |
| protocol HomeBusinessLogic: class { |
| |
| /// |
| func fetchUser(_ request: HomeModels.FetchUser.Request) |
| } |
| |
| final class HomeInteractor: HomeBusinessLogic { |
| |
| /// |
| var presenter: HomePresentationLogic? |
| |
| func fetchUser(_ request: HomeModels.FetchUser.Request) { |
| // |
| // |
| let userPhone = "+7 (999) 111-22-33" |
| let userEmail = "im@alekseypleshkov.ru" |
| // ... |
| // Presentor' |
| let response = HomeModels.FetchUser.Response(userPhone: userPhone, userEmail: userEmail) |
| |
| // Presentor' |
| presenter?.presentUser(response) |
| } |
| } |
主讲人
它具有指向
DisplayLogic协议的链接,在该协议下对
ViewController进行了签名。 它不包含任何业务逻辑,而仅在显示接收到的数据之前对其进行格式化。 在该示例中,我们格式化了电话号码,准备了
ViewModel结构的实例,然后使用
DisplayLogic协议中的
displayUser(viewModel :)方法将其传递给
ViewController ,其中已在其中显示数据。
| /// |
| protocol HomePresentationLogic: class { |
| |
| /// Interactor'a |
| func presentUser(_ response: HomeModels.FetchUser.Response) |
| } |
| |
| final class HomePresenter: HomePresentationLogic { |
| |
| /// View Controller'a |
| weak var viewController: HomeDisplayLogic? |
| |
| func presentUser(_ response: HomeModels.FetchUser.Response) { |
| // |
| let formattedPhone = response.userPhone.replacingOccurrences(of: "-", with: " ") |
| |
| // ViewModel View Controller |
| let viewModel = HomeModels.FetchUser.ViewModel(userPhone: formattedPhone, userEmail: response.userEmail) |
| |
| // View Controller'a |
| viewController?.displayUser(viewModel) |
| } |
| } |
结论
通过这种体系结构,我们有机会分配职责,提高测试应用程序的便利性,介绍实现的各个部分的可替换性以及编写团队协作代码的标准。
感谢您阅读到最后。
系列文章
- 了解Clean Swift架构(您在这里)
- Clean Swift体系结构中的路由器和数据传递
- 干净迅速的建筑工人
- Clean Swift架构中的单元测试
- 一个简单的在线商店架构Clean Swift的示例
场景的所有组成部分:
链接撰写文章的帮助:
Bastien