kotlin和mvvm
Over many years iOS Engineers have explored and experimented different architectural styles like MVC, MVVM, VIP, VIPER and many more. After 11 long years, Apple have decided to move away from an Event-Driven, Imperative UIKit to a State-Driven, Declarative SwiftUI. With SwiftUI’s State driven characteristic, along with Reactive Combine framework, MVVM fits in naturally as an architectural pattern.
多年以来,iOS工程师一直在探索和试验不同的架构样式,例如MVC,MVVM,VIP,VIPER等。 经过11年的努力,Apple决定从事件驱动的命令式UIKit转向状态驱动的声明式SwiftUI 。 凭借SwiftUI的状态驱动特性以及Reactive Combine框架,MVVM自然适合作为架构模式。
Core components in MVVM
MVVM中的核心组件
- Model: It represents your domain model and contains all the business logic. It will not have any knowledge on how the view would be presented to user. 模型:它代表您的领域模型,并包含所有业务逻辑。 它对如何将视图呈现给用户一无所知。
- View: This is passive and doesn’t have any knowledge on business. It’s just a visual representation of ViewModel. 视图:这是被动的,对业务没有任何了解。 这只是ViewModel的直观表示。
- View Model: It represents a State of the View at any given point of time. It will also contain the Presentation logic. ViewModel transforms the Model in a way that view can consume directly. When there’s a change in the model, ViewModel informs View about the change, mostly through binding. 视图模型:它表示在任何给定时间点的视图状态。 它还将包含Presentation逻辑。 ViewModel以视图可以直接使用的方式转换Model。 当模型发生更改时,ViewModel主要通过绑定将更改通知给View。
Let’s take a simple example of Stopwatch to architect our SwiftUI app with MVVM.
让我们以秒表为例,使用MVVM构建SwiftUI应用。
UI consist of:
用户界面包括:
- Text to show Time Elapsed. 显示经过时间的文字。
- Below the Text we have two buttons. One to Start/Stop the Stopwatch and the other to record the Lap time. 在文本下方,我们有两个按钮。 一个用于启动/停止秒表,另一个用于记录间隔时间。
- We also have a List to display all the recorded Lap times. 我们还有一个列表来显示所有记录的圈数时间。
Let’s take a closer look at the Model
让我们仔细看看模型
![Image for post](https://miro.medium.com/max/9999/1*auakdwLrKzhinJCFhiYDyw.png)
We have a simple model to publish the time elapsed to interested parties .
我们有一个简单的模型可以将经过的时间发布给感兴趣的各方。
Some Foundation types have already exposed the publisher functionality for Combine. Timer being one of those, we can invoke Timer.publish(every: 1.0, on: RunLoop.main, in: .default) to get a Timer Publisher.
一些基金会类型已经公开了Combine的发布者功能。 计时器就是其中之一,我们可以调用Timer.publish(每个:1.0,位于:RunLoop.main,位于:.default中)以获得一个计时器发布者。
Timer is a Connectable Publisher, which means, Subscriber has to connect manually to start receiving published values. We can also invoke autoconnect on the publisher to let the subscriber start receiving values as soon as it is connected.
Timer是可连接的发布者,这意味着订阅者必须手动连接才能开始接收发布的值。 我们也可以调用在发布自动连接到只要它连接让用户开始接收值。
We use a built-in subscriber called Sink to receive values over the time. Sink returns AnyCancellable, a type erased Cancellable, which we keep a reference to, so that we can cancel the subscription upon Stop message.
我们使用一个称为Sink的内置订户来接收一段时间内的值。 Sink返回AnyCancellable,这是一种擦除的Cancellable类型,我们保留对其进行引用,以便我们可以在Stop消息后取消订阅。
When Timer is fired, we increment timeElapsed by 1. Note that we have marked our timeElapsed with @Published property wrapper.
触发Timer后,我们将timeElapsed递增1。请注意,我们已使用@Published属性包装器标记了timeElapsed。
@Published is a useful Property Wrapper to make a property a Publisher . So whenever we update its value, the update is published to all its subscribers.
@Published是有用的Property Wrapper,用于将属性设为Publisher。 因此,每当我们更新其值时,更新就会发布给所有订阅者。
ViewModel
视图模型
![Image for post](https://miro.medium.com/max/9999/1*HfGj3XNaFU6-fk99tK9tSg.png)
Our ViewModel conforms to ObservableObject. Anything that conforms to ObservableObject can be used inside SwiftUI and be subscribed.
我们的ViewModel符合ObservableObject。 任何符合ObservableObject的东西都可以在SwiftUI中使用并进行订阅。
elapsedTime publishes the elapsed time. Note that the type is String so that view can directly use its value without any transformation.
elapsedTime发布经过的时间。 请注意,类型为String,因此视图可以直接使用其值而无需任何转换。
laps publishes the recorded lap times
单圈发布记录的单圈时间
buttonText publishes the text of Start/Stop button
buttonText发布“开始/停止”按钮的文本
isLapButtonDisabled publishes whether or not Lap Button is enabled. Lap button should be disabled when StopWatch is not running.
isLapButtonDisabled发布是否启用了膝上按钮。 当秒表未运行时,应禁用膝上按钮。
cancellable we also keep a reference to the Cancellable returned while subscribing the model publisher. This is needed to cleanup later.
cancellable,我们在订阅模型发布者时也会保留对返回的Cancellable的引用。 以后需要清理。
clock ViewModel will have a reference to the Model
时钟 ViewModel将具有对模型的引用
View can subscribe to these Publisher properties and get notified whenever its values change.
View可以订阅这些Publisher属性,并在其值更改时得到通知。
![Image for post](https://miro.medium.com/max/9999/1*wGCjgNRkTJ3uDsimFEAX4w.png)
In init method, we subscribe timeElapsed publisher in the model so that we get notified when there’s a change. Since timeElapsed publisher publishes Int, we need to transform the type to String with map operator. We then use assign Subscriber to assign it our elapsedTime property. Since elapsedTime is a publisher, it immediately publishes the new value to its subscribers.
在init方法中,我们在模型中订阅timeElapsed发布者,以便在发生更改时得到通知。 由于timeElapsed发布者发布Int ,因此我们需要使用map将类型转换为String 操作员。 然后,我们使用Assign Subscriber为我们分配elapsedTime属性。 由于elapsedTime是发布者,因此它立即将新值发布给其订阅者。
Like Sink, assign also returns AnyCancellable. We need to keep a reference to this to do any cleanup at a later point.
像接收器一样,assign也返回AnyCancellable。 我们需要保留对此的引用,以便稍后进行任何清理。
![Image for post](https://miro.medium.com/max/9999/1*Xj1Lty72sl6nka852AINEQ.png)
When ViewModel receives a buttonTapped message, we update our state to either Running or Not Running based on current state. When ViewModel receives a lap message, we simply append the current elapsed time to the laps property.
当ViewModel收到buttonTapped消息时,我们会根据当前状态将状态更新为“正在运行”或“未运行”。 当ViewModel收到圈速消息时,我们只需将当前经过的时间附加到laps属性即可。
![Image for post](https://miro.medium.com/max/9999/1*Za4MoJhmPmPY-aD_TsM-5Q.png)
If new state is Running, Update button text to “Stop”, enable lap button and ask clock to start. Otherwise update button text to “Start”, disable lap button and ask clock to stop.
如果新状态为“正在运行”,请将按钮文本更新为“停止”,启用单圈按钮并要求时钟启动。 否则,将按钮文字更新为“开始”,禁用单圈按钮并要求时钟停止。
View
视图
![Image for post](https://miro.medium.com/max/9999/1*Zx5x2nNGLQqTb2r6i1eD2Q.png)
View has become a mere representation of ViewModel. View has an @ObservedObject ViewModel property. So whenever there is a change in any property inside ViewModel, View is updated to match the current state.
视图已成为ViewModel的单纯表示。 视图具有@ObservedObject ViewModel属性。 因此,只要ViewModel中的任何属性发生更改,View都会更新以匹配当前状态。
- At the top level we have a Navigation view which embeds a VStack. 在顶层,我们有一个导航视图,该视图嵌入了VStack。
- First item inside VStack is a Text representing elapsed time. It’s value is bound to ViewModels’s elapsedTime. VStack中的第一项是表示经过时间的文本。 它的值绑定到ViewModels的elapsedTime。
- Then we have a HStack to show Start/Stop button and Lap button. Button actions are simply forwarded to ViewModel instead of View managing the current State. 然后我们有一个HStack来显示Start / Stop按钮和Lap按钮。 只需将按钮操作转发到ViewModel即可,而不是由View管理当前状态。
- Then we have a List whose items are bound to ViewModel’s “lap” property. 然后我们有一个List,其项绑定到ViewModel的“ lap”属性。
Conclusion
结论
Even though Mac development environment provided a two-way binding mechanism through KVO, iOS never had one such mechanism until the release of Combine framework. With the introduction of Combine and SwiftUI we do not have to integrate libraries like RxSwift or ReactiveCocoa to get started with reactive programming. It’s a lot simpler now and easy to reason about the code.
尽管Mac开发环境通过KVO提供了一种双向绑定机制,但iOS在发布Combine框架之前从来没有这样一种机制。 随着Combine和SwiftUI的引入,我们无需集成RxSwift或ReactiveCocoa之类的库即可开始进行React式编程。 现在,它变得更加简单,并且易于推理代码。
This might be a simple Use-Case to understand the whole of Combine and SwiftUI but this could be a starting point to get your hands dirty on Combine. In the next article we’ll look at how to leverage the power of dependency injection to create a more testable and agile architecture.
这可能是一个简单的用例,用于了解Combine和SwiftUI的全部内容,但这可能是让您开始使用Combine的起点。 在下一篇文章中,我们将研究如何利用依赖注入的功能来创建一个更具可测试性和敏捷性的体系结构。
翻译自: https://medium.com/swlh/introduction-to-mvvm-with-swiftui-and-combine-f2f7a0dc8585
kotlin和mvvm