ios 解耦_Android iOS View逻辑解耦示例

ios 解耦

To make our app code better structured, one would separate the logic from the view — e.g. Activity (in Android) and ViewController (in iOS). Hence, it’s important to know how to do so.

为了使我们的应用程序代码更好地结构化,可以将逻辑与视图分开,例如Activity (在Android中)和ViewController (在iOS中)。 因此,重要的是要知道如何去做。

Here, I share various approaches to decoupling the logic from the view, with examples in both Android (Kotlin) and iOS (Swift). This makes learning both platforms easier.

在这里,我以Android(Kotlin)和iOS(Swift)中的示例为例,分享了各种从视图中分离逻辑的方法。 这使得学习两个平台变得更加容易。

This will also expose architectures like MVP to MVVM. You can evaluate which is better for individual apps.

这还将向MVM公开MVP之类的架构。 您可以评估哪个更适合单个应用。

程式码范例 (Code Examples)

To aid learning, I’ve created code examples on both platforms with six examples (the six buttons) that do the exact same thing (from the user perspective) but that have different architectural approaches that I’ll elaborate on in each section below.

为了帮助学习,我在两个平台上都创建了带有六个示例(六个按钮)的代码示例,这些示例执行完全相同的操作(从用户角度而言),但具有不同的体系结构方法,我将在下面的每个部分中详细说明。

Image for post

基本:无架构 (Basic: No Architecture)

To begin, let’s look at one without any architecture. This is handy for illustrating what the overall functionality is. It’s triggered by the cyan-colored button. The code is easy to trace, as all codes (including the logic) are in Activity and ViewController.

首先,让我们看一个没有任何架构的架构。 这对于说明整体功能很方便。 它由青色按钮触发。 该代码易于跟踪,因为所有代码(包括逻辑)都在ActivityViewController

The functionality is simple: We’ll show an editable text field and a Save button. We call this the edit mode. Once a user types something and clicks the Save button, it’ll change into a label with the text shown inside. It’ll also display a Clear button. We call this the view mode.

功能很简单:我们将显示一个可编辑的文本字段和一个保存按钮。 我们称其为编辑模式。 用户键入内容并单击“保存”按钮后,它将变为带有内部文本的标签。 它还将显示一个清除按钮。 我们称其为查看模式。

Inversely, when the user clicks the Clear button, it’ll return to the edit mode and clear the text.

相反,当用户单击“清除”按钮时,它将返回到编辑模式并清除文本。

Image for post

Other than changing from edit mode to a view mode and vice versa, it also persists the type data to memory. So in case of the user exiting from the view when in view mode and returning to the page, it’ll show the edit mode.

除了从编辑模式更改为查看模式(反之亦然)之外,它还将类型数据持久保存到内存中。 因此,如果用户在视图模式下退出视图并返回页面,它将显示编辑模式。

Functionally, it works as shown in the flow chart as below. There are a few examples of logic in the flow.

从功能上讲,它的工作原理如下图所示。 流程中有一些逻辑示例。

Image for post

From an architectural diagram, there’s just no separation of logic. Everything is handled within a class (Activity/ViewController).

从体系结构图来看,逻辑之间没有任何分离。 一切都在类( Activity / ViewController )中处理。

Image for post

This is OK for a small logic-view page, as in this example. But if we expand its functionality further, it won’t be scalable, and it'll become hard to maintain. Besides, it’ll make the testing hard, as we need to include the View class in our test code.

如本例所示,对于一个小的逻辑视图页面来说,这是可以的。 但是,如果我们进一步扩展其功能,它将无法扩展,并且将变得难以维护。 此外,这将使测试变得困难,因为我们需要在测试代码中包含View类。

To make it better and scaleable in the long run, let’s decouple the logic out into another class named ViewModel. As for how — below are the five different approaches.

为了使它从长远来看更好和可伸缩,让我们将逻辑分离到另一个名为ViewModel类中。 至于如何-以下是五种不同的方法。

代表架构 (Delegate Architecture)

In the Android world, this is usually called the model-view-presenter (MVP) architectural pattern. In iOS, it’s usually referred to as the delegate approach, as the main class delegates its logic to another class. Here, we refer to the logic handling class ViewModel.

在Android世界中,这通常称为模型视图呈现器(MVP)架构模式。 在iOS中,通常将其称为委托方法,因为主类将其逻辑委托给另一个类。 在这里,我们指的是逻辑处理类ViewModel

This ViewModel class will be responsible for all of the logic section (e.g., deciding if the Save button can save or not) as well as be interacting with the persisted text, offloading the View class from worrying about all this detail.

ViewModel类将负责所有逻辑部分(例如,确定“保存”按钮是否可以保存),并与保留的文本进行交互,从而使View类无需担心所有这些细节。

The ViewModel, though responsible for the logic, needs to communicate back to the view to get into the view or edit mode. To do that, we’ll need the help of interface/protocol as shown in the diagram below.

尽管ViewModel负责逻辑,但仍需要与视图进行通信才能进入视图或编辑模式。 为此,我们需要接口/协议的帮助,如下图所示。

Image for post

The interface/protocol define what the view needs to implement so the ViewModel can interact back.

接口/协议定义视图需要实现的内容,以便ViewModel可以向后交互。

// Androidinterface DelegateView {
fun enterViewMode(text: String)
fun enterEditMode()
}// iOS
protocol
DelegateView : class {
func enterViewMode(text: String)
func enterEditMode()
}

The view itself would then implement this Interface/Protocol accordingly. It owns a ViewModel, where it passes itself into.

然后,视图本身将相应地实现此接口/协议。 它拥有一个ViewModel ,并将其传递给它。

// Android
class DelegateActivity : AppCompatActivity(), DelegateView {
private var viewModel: DelegateViewModel? = null
//...
viewModel = DelegateViewModel(this)
}// iOSclass DelegateViewController: UIViewController, DelegateView {
private var viewModel: DelegateViewModel?
//...
self.viewModel = DelegateViewModel(delegate: self)
}

To communicate back, the ViewModel also has a reference of the view, which is the implementation of the interface/protocol.

为了进行回传, ViewModel还具有视图的引用,该引用是接口/协议的实现。

// Android
class DelegateViewModel(private val delegate: DelegateView)// iOS
class DelegateViewModel {
private unowned let delegate: DelegateView!
init(delegate: DelegateView) { self.delegate = delegate } // ...
}

Here, it’s important to note that in iOS, we have the unowned type set to the delegate to break the retaining cycle.

在这里,需要特别注意的是,在iOS中,我们为delegate设置了unowned类型,以打破保留周期。

That’s it. You can now decouple logic from the view. Check out the example code for details.

而已。 您现在可以将逻辑与视图分离。 查看示例代码以获取详细信息。

讨论区 (Discussion)

This approach is simple to trace and has a strong contract between the view and the ViewModel — as defined by the interface/protocol. This makes tracing easy, and compile-time errors are shown easily when something is missing.

这种方法易于跟踪,并且在视图和ViewModel之间具有很强的ViewModel -由接口/协议定义。 这使跟踪变得容易,并且在缺少某些内容时很容易显示出编译时错误。

The disadvantages of this approach are:

这种方法的缺点是:

  1. An extra interface/protocol is needed.

    需要一个额外的接口/协议。
  2. Even though we’ve decoupled the logic, they’re still somehow strongly coupled, where one needs to have the knowledge of what’s expected by the other side.

    即使我们已经分离了逻辑,但是它们仍然以某种方式紧密地耦合在一起,一个地方需要了解另一边的期望。
  3. Even if not all the interface/protocol defined is required, the view would still need to implement them (we could use the default interface in Kotlin to mitigate that — but not for the protocol).

    即使并不需要所有定义的接口/协议,该视图仍将需要实现它们(我们可以使用Kotlin中的默认接口来减轻这种情况-但不适用于协议)。

To solve this limitation, let’s look into the next approach

为了解决此限制,让我们研究下一种方法

功能架构 (Functional Architecture)

Instead of defining the interface/protocol with the modern programming approach, we could pass in the function instead. This could be done using lambdas in Kotlin and closures in Swift.

不用现代编程方法定义接口/协议,我们可以传入函数。 可以使用Kotlin中的lambda和Swift中的闭包来完成。

Image for post

In the ViewModel, the required lambda/closure definition could be declared as below:

ViewModel ,可以如下声明所需的lambda / closure定义:

// Android
fun initialSetup(enterEditMode: () -> Unit,
enterViewMode: (text: String) -> Unit) // iOS
fun initialSetup(enterEditMode: () -> Unit,
enterViewMode: (text: String) -> Unit)

In the view, one could then send the function over easily:

在视图中,然后可以轻松地将函数发送过来:

// Android
viewModel?.initialSetup(::enterEditMode, ::enterViewMode)// iOS
viewModel?.initialSetup(enterEditMode: enterEditMode,
enterViewMode: enterViewMode)

讨论区 (Discussion)

In this approach, we’ve eliminated the need for the interface/protocol as shown in the diagram above. The strong contract and coupling aren’t there anymore. This makes the view have more control over what it needs and doesn’t need to define in the ViewModel.

通过这种方法,我们消除了对接口/协议的需求,如上图所示。 强大的合同和耦合不再存在。 这使视图可以更好地控制其需要和不需要在ViewModel定义的内容。

However, the disadvantages of this approach are:

但是,此方法的缺点是:

  1. The view decides what functionality the ViewModel should call. And, hence, if a wrong function (of the same signature) is sent through, this could result in a bug that the ViewModel has no control over.

    该视图确定ViewModel应该调用什么功能。 因此,如果发送了错误的(具有相同签名的)函数,则可能导致ViewModel无法控制的错误。

  2. If the same function is required over and over, the same function needs to be sent over and over again to the ViewModel. For example, the code shown below shows enterViewMode being sent twice and enterEditMode is being sent twice too.

    如果一遍又一遍地需要相同的功能,则需要将相同的功能一遍又一遍发送到ViewModel 。 例如,下面的代码显示enterViewMode被发送两次, enterEditMode也被发送两次。

// Android example
private fun save() {
viewModel?.save(edit_text.text.toString(), ::enterViewMode)
}
private fun clear() {
viewModel?.clear(::enterEditMode)
}
private fun setupInitialView() {
viewModel?.initialSetup(::enterEditMode, ::enterViewMode)
}

通知架构 (Notification Architecture)

This isn’t a very common pattern for a simple app, but it’s worth visiting before we look into the reactive approach.

对于一个简单的应用程序来说,这不是很常见的模式,但是在研究React式方法之前,值得一游。

In this approach, we further decouple the view and the ViewModel — where the view interacts with the ViewModel, but the ViewModel doesn’t communicate back to the view directly.

在这种方法中,我们进一步将视图与ViewModel —视图与ViewModel进行交互,但是ViewModel不会直接与视图通信。

Instead, the ViewModel just update the model (the data). Upon update, the data will send a notification to those listening to its notification. The view will register to listen to that event and, hence, would be notified of the changes.

相反, ViewModel只是更新模型(数据)。 更新后,数据将向那些监听其通知的人发送通知。 视图将注册以侦听该事件,因此将被通知更改。

Image for post

In my code example, I use Android and iOS’s natively provided notification mechanism.

在我的代码示例中,我使用Android和iOS本地提供的通知机制。

Note: LocalBroadcastManager is no longer recommended in Android. I use it — as it’s relatively similar to NotificationCenter in iOS. Google recommended using another observer pattern (e.g., EventBus) — or even LiveData — for a more current way of doing this. Check out this blog post for more information on LiveData.

注意: Android中不再建议使用LocalBroadcastManager。 我使用它-因为它与iOS中的NotificationCenter相对类似。 Google建议使用其他观察者模式(例如EventBus )或什至LiveData,以实现更最新的方式。 查看此博客文章以获取有关LiveData的更多信息。

In this approach, the view will first register as a receiver.

在这种方法中,视图将首先注册为接收者。

// Androidclass NotificationArchitectureActivity : AppCompatActivity() {  private val messageReceiver: BroadcastReceiver 
= object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
//...
}
} // ...LocalBroadcastManager.getInstance(this).registerReceiver(
messageReceiver,
IntentFilter(NotificationModel.textSetNotification)
)}// iOSclass NotificationViewController: UIViewController {
override init(nibName nibNameOrNil: String?,
bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) NotificationCenter.default.addObserver(
self, selector: #selector(gotNotified(_:)),
name: NSNotification.Name(rawValue:
NotificationModel.textSetNotification),
object: nil)
}
}

Also, it’d need to remember to unregister when exiting the view.

另外,在退出视图时还需要记住注销。

// Android
override fun onDestroy() {LocalBroadcastManager.getInstance(this).unregisterReceiver(
messageReceiver)
super.onDestroy()
}// iOSdeinit {NotificationCenter.default.removeObserver(
self, name: NSNotification.Name(rawValue:
NotificationModel.textSetNotification),
object: self)
}

The ViewModel will have some logic deciding when it should send an update to the model text. And when the model text is updated, it’ll then broadcast out its text.

ViewModel具有确定何时将更新发送给模型文本的逻辑。 当模型文本更新时,它将广播其文本。

// Androidvar text: String? = MainActivity.persistedText
set(value) {
field = value
MainActivity.persistedText = value ?: String()
val intent = Intent(textSetNotification).apply {
putExtra(textKey, value)
}
LocalBroadcastManager.getInstance
(
MainActivity.globalContext).sendBroadcast(intent)
}// iOSvar text: String? {
didSet {
ViewController.persistedText = text ?? String()
let data:[String: String?]
= [NotificationModel.textKey: text] NotificationCenter.default.post(
name: NSNotification.Name(
rawValue: NotificationModel.textSetNotification),
object: nil, userInfo: data as [AnyHashable : Any]
)
}
}

Here, as the broadcaster only broadcasts the model-text change, which could change from having text to not having text, the view would still need to have some logic that decides what to enter into edit mode or view mode.

在这里,由于广播公司仅广播模型文本更改,该更改可能会从有文本变为无文本,因此视图仍需要具有一些逻辑来决定要进入编辑模式或视图模式的逻辑。

讨论区 (Discussion)

This pattern seems nice, as it makes the ViewModel not need to know about the view at all. Its responsibility is just to update the model text (data). It’s the responsibility of the model text (data) to broadcast that it has changed.

这种模式看起来不错,因为它使ViewModel完全不需要了解视图。 它的责任只是更新模型文本(数据)。 模型文本(数据)负责广播已更改的内容。

This pattern was once preferred due to its neat decoupling. However, it introduces complexity to one’s understanding of the flow. At times, it may seem magical — maybe the view would just know itself that the data has been updated. Tracing it back to the origin point of data sent isn’t trivial.

由于其纯净的去耦,该模式曾经是首选。 然而,它给人们对流程的理解带来了复杂性。 有时,这似乎很神奇-也许视图只会知道数据已被更新。 将其追溯到发送数据的原始点并非易事。

Note: This pattern is also used by a once-popular loader architecture in Android. The pattern will have the view get the ViewModel to fetch the data and update the database. Upon updating the database, it’ll notify the view it has changed and send the changed data over. This approach is also considered deprecated by Google now, with LiveData seen as the preferred way.

注意: Android中曾经流行的加载器架构也使用此模式。 该模式将使视图获得ViewModel来获取数据并更新数据库。 更新数据库后,它将通知视图已更改,并将更改后的数据发送过来。 Google现在也已不赞成使用此方法,LiveData被视为首选方法。

React架构 (Reactive Architecture)

The notification architecture is neat due to its great decoupling. However, its difficulty in tracing the flow brings its downfall. Hence, the reactive approach came into play. It has great decoupling and a simpler way to trace how they interact.

通知体系结构很好,因为它具有很好的去耦性。 但是,它在跟踪流量方面的困难带来了崩溃。 因此,React性方法开始起作用。 它具有出色的去耦和跟踪它们如何相互作用的更简单方法。

I call this the reactive approach, as we’re using the reactive-programming mechanism. However, in the development community, this is also known as the model-view-view-model (MVVM) architectural pattern.

我将其称为React式方法,因为我们正在使用React式编程机制。 但是,在开发社区中,这也称为模型-视图-视图-模型(MVVM)架构模式。

In this reactive architecture, the view will assign the ViewModel to perform its needed process logic. The ViewModel has the reactive object (e.g. the Subject, Signal, Observable, etc), that the ViewModel can update accordingly.

在这种React式体系结构中,视图将分配ViewModel来执行其所需的过程逻辑。 ViewModel具有React对象(例如SubjectSignalObservable等),该ViewModel可以相应地更新。

Image for post

The view will subscribe/bind/observe the reactive object. Hence, upon any changes on the reactive object, the view will be notified of the change immediately. Although it seems similar to the notification approach, it’s much lighter weight and efficient when it comes to its capabilities of transforming the data.

该视图将订阅/绑定/观察React对象。 因此,在React对象发生任何更改时,将立即将更改通知视图。 尽管它看起来与通知方法相似,但是在转换数据的功能方面却轻巧得多且效率更高。

In order to use the reactive approach, we’d need to use external library.

为了使用React性方法,我们需要使用外部库。

To learn more about RxJava, you could refer to this blog post.

要了解有关RxJava的更多信息,请参阅此博客文章

In this approach, in the ViewModel, it has the reactive object. When needed, it’d update the reactive object with the appropriate data.

用这种方法,在ViewModel ,它具有React对象。 需要时,它将使用适当的数据更新React性对象。

// Android
val textSubject: ReplaySubject<String> = ReplaySubject.create()
// ...
textSubject.onNext(text)// iOS
let textSubject = ReplayOneSubject<String, Never>()
//...
textSubject.send(text)

In the view, we’ll subscribe/bind/observe the respective reactive object in the ViewModel.

在视图中,我们将订阅/绑定/观察ViewModel的相应React对象。

// Android
disposable = viewModel?.textSubject?.subscribe { text ->
if (text.isEmpty()) {
enterEditMode()
} else {
enterViewMode(text)
}
}// iOSviewModel?.textSubject.observeNext { [unowned self] text in
if text.isEmpty {
self.enterEditMode()
} else {
self.enterViewMode(text: text)
}
}.dispose(in: bag)

Upon receiving the signal, it then extracts the data and decides what to do with it. There’s still some logic here in the view, as you see. It seems not ideal.

一旦接收到信号,它便提取数据并决定如何处理它。 如您所见,视图中仍然存在一些逻辑。 似乎并不理想。

However, this is because I use one reactive object to communicate that’ll be used to decide various views changes in the view.

但是,这是因为我使用一个React性对象进行通信,该对象将用于确定视图中的各种视图更改。

To make it better and reduce the logic needed in the view, we could consider using more reactive objects — as shown in the next approach.

为了使其更好并减少视图中所需的逻辑,我们可以考虑使用更多的React性对象,如下面的方法所示。

全React式架构 (Full-Reactive Architecture)

This approach is exactly the same as above except we use more reactive objects. We can tie together objects of a particular view behavior.

除了我们使用更多的React对象之外,该方法与上面的方法完全相同。 我们可以将特定视图行为的对象捆绑在一起。

Image for post

Let’s enlarge this further to look at the relationship between the reactive objects and the view’s component.

让我们进一步放大一下,以查看React对象与视图组件之间的关系。

Image for post

Such a 1:1 relationship enables the full logic control in the ViewModel. Hence, this moves all possible logic from the view, making the view dependent upon the ViewModel.

这样的1:1关系启用了ViewModel的完整逻辑控制。 因此,这将所有可能的逻辑从视图中移出,从而使视图依赖于ViewModel

Below, we show the binding relationship between the view’s components and the reactive objects.

下面,我们显示视图的组件和React对象之间的绑定关系。

// Android
viewModel?.textSubject?.subscribe { text ->edit_text.setText(text)
text_view.text = text }?.addToBag()
viewModel?.modeTextSignal?.subscribe { text_status.text = it }?.addToBag()
viewModel?.hideKeyboardSignal?.subscribe { hideKeyboard() }?.addToBag()
viewModel?.hideClearButton?.subscribe { btn_clear.hideIt(it) }?.addToBag()
viewModel?.hideTextField?.subscribe { edit_text.hideIt(it)}?.addToBag()
viewModel?.hideSaveButton?.subscribe { btn_save.hideIt(it) }?.addToBag()
viewModel?.hideTextLabel?.subscribe { text_view.hideIt(it) }?.addToBag()// iOS
viewModel?.textSubject.observeNext { [unowned self] text in
self.textField.text = text
self.textLabel.text = text }.dispose(in: bag)viewModel?.modeTextSignal.observeNext {
self.statusLabel.text = $0 }.dispose(in: bag)viewModel?.hideKeyboardSignal.observeNext{
self.textField.resignFirstResponder() }.dispose(in: bag)viewModel?.hideTextLabel
.bind(to: textLabel.reactive.isHidden).dispose(in: bag)viewModel?.hideClearButton
.bind(to: clearButton.reactive.isHidden).dispose(in: bag)viewModel?.hideTextField
.bind(to: textField.reactive.isHidden).dispose(in: bag)viewModel?.hideSaveButton
.bind(to: saveButton.reactive.isHidden).dispose(in: bag)

The reactive approach is one of the more popular approaches these days, due to:

由于以下原因,React式方法是当今最受欢迎的方法之一:

  • Its capability to decouple the ViewModel from the view, where the ViewModel doesn’t need to have knowledge of the view at all

    它具有将ViewModel与视图解耦的功能,其中ViewModel完全不需要了解视图

  • The reactive object is much more lightweight than the notification framework, and, hence, we can have them individually for each component

    React性对象比通知框架轻得多,因此,我们可以为每个组件分别设置它们
  • Tracing the logic — though not as simple as the delegate approach — is still feasible in general

    跟踪逻辑(尽管不如委托方法那么简单)总体上还是可行的

结论 (Conclusion)

Given that the reactive-MVVM approach has a great advantage, it also has been adopted by Google-made architecture for Android — e.g. Android Architecture Components with LiveData, etc.

鉴于React式MVVM方法具有很大的优势,它也已被Google制造的Android架构所采用,例如具有LiveData的Android架构组件等。

Thanks for reading.

谢谢阅读。

翻译自: https://medium.com/better-programming/android-ios-view-logic-decoupling-examples-8be1bc287114

ios 解耦

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值