Architecture

本文深入探讨了MVVM架构如何与现有MVC架构兼容,使其应用更易于测试,并强调了绑定机制的重要性。同时,文章阐述了避免滥用单例模式、引入行为模式在iOS应用中的实践以及使用依赖注入的技术。最后,介绍了VIPER架构的设计原则,包括View、Interactor、Presenter、Entity和Routing等组件,以及如何将业务逻辑与用户界面分离,提高代码可维护性和测试性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Introduction to MVVM

*MVVM is compatible with your existing MVC architecture.

MVVM makes your apps more testable.

MVVM works best with a binding mechanism.


Avoiding Singleton Abuse

*Most developers agree that global mutable state is a bad thing. Statefulness makes programs hard to understand and hard to debug. We object-oriented programmers have much to learn from functional programming, in terms of minimizing the statefulness of code.

Persistent state is the enemy of unit testing, since unit testing is made effective by each test being independent of all other tests. If state is left behind from one test to another, then the order of execution of tests suddenly matters.


*The lesson here is that singletons should be preserved only for state that is global, and not tied to any scope. If state is scoped to any session shorter than "a complete lifecycle of my app," that state should not be managed by a singleton. A singleton that's managing user-specific state is a code smell, and you should critically reevaluate the design of your object graph.


*The technique of passing a dependency into a dependent object is more formally referred to as dependency injection.


Behaviors in iOS Apps

*There are many patterns we can use to make it happen, and one of the best ones is composition. Composition makes it easier to follow the Single Responsibility Principle and simplify our classes. 

Instead of having a Massive View Controller that serves multiple different roles (like data sources and delegates), you separate those roles into different classes. The view controller can then just be responsible for configuring them and coordinating the work. After all, the less code we write, the less code we need to debug and maintain.


*A behavior is an object responsible for implementing a specific role, e.g. you can have a behavior that implements a parallax animation.


*Using behaviors means moving lots of the code from view controllers into separate classes. If you use behaviors, you usually end up with very lightweight view controllers.

Because a behavior is responsible for just a single role, it's easy to avoid dependencies between behavior-specific and application-specific logic. This allows you to share the same behaviors across different applications.

Behaviors are small classes that work like a black box. This means they are very easy to cover with unit tests. You could test them without even creating real views, and instead by supplying mock objects.


*Runtime attributes are one of the key features of using Interface Builder. They offer you a way to set up custom classes and even set properties on iOS's built-in classes.


*Instead of manually setting up a strong reference to the behavior from the view controller, we make the behavior assign itself as an associated object of the view controller as part of the configuration process, if needed.


*It's very useful to have behaviors be able to post events, e.g. when an animation finishes. One can enable that in Interface Builder by making behaviors inherit from UIControl. Then a specific behavior can just call:

[self sendActionsForControlEvents:UIControlEventValueChanged];

This will allow you to connect events from behaviors to your view controller code.


Subclassing

*Mantle: make it easy to write a simple model layer for your Cocoa or Cocoa Touch application.


*A case where subclassing might be very helpful is in model objects. Most of the time, my model objects inherit from a class that implements isEqual:, hash, copyWithZone:, and description. These methods are implemented once by iterating over the properties, making it a lot harder to make mistakes.(If you're looking for a base class like this,)


*If your subclasses merely share the same interface, protocols can be a very good alternative. If you know an object needs to be modified a lot, you might want to use delegates to dynamically change and configure it. When you want to extend an existing object with some simple functionality, categories might be an option. When you have a set of subclasses that each override the same methods, you might instead use configuration objects. And finally, when you want to reuse some functionality, it might be better to compose multiple objects instead of extending them.


*Often, a reason to use subclassing is when you want to make sure that an object responds to certain messages.

Most likely, the two classes don't share a lot of code, just the same interface. When that's the case, it might be a good solution to use protocols instead. 


*Alternative: Configuration Objects

You can make your code a bit simpler by using configuration objects. You can keep the shared logic in the Theme class (e.g. slide layout), but move the configuration into simpler objects that only have properties. 


*Alternative: Composition

A better approach is to store this dictionary in a private property (or instance variable), and only expose the two cache methods. Now, you maintain the flexibility to change the implementation as you gain more insight, and consumers of your class don't need to be refactored.


Architecting iOS Apps with VIPER

*It's important to design our code so that each piece is easily identifiable, has a specific and obvious purpose, and fits together with other pieces in a logical fashion. This is what we call software architecture. Good architecture is not what makes a product successful, but it does make a product maintainable and helps preserve the sanity of the people maintaining it!


The word VIPER is a backronym for View, Interactor, Presenter, Entity, and Routing. Clean Architecture divides an app's logical structure into distinct layers of responsibility. This makes it easier to isolate dependencies (e.g. your database) and to test the interactions at the boundaries between layers.


*A use case is the layer of an application that is responsible for business logic. Use cases should be independent from the user interface implementation of them. They should also be small and well-defined. Deciding how to break down a complex app into smaller use cases is challenging and requires practice, but it's a helpful way to limit the scope of each problem you are solving and each class that you are writing.


*The main parts of VIPER:

View: displays what is told to by the Presenter and relays user input back to the Presenter.

Interactor: contains the business logic as specified by a use case.

Presenter: contains view logic for preparing content for display (as received from the Interactor) and for reacting to user inputs (by requesting new data from the Interactor).

Entity: contains basic mode objects used by the Interactor.

Routing: contains navigation logic for describing which screens are shown in which order.

This separation also conforms to the Single Responsibility Principle. The Interactor is responsible to the business analyst, the Presenter represents the interaction designer, and the View is responsible to the visual designer.


*Interactor

An Interactor represents a single use case in the app. It contains the business logic to manipulate model objects (Entities) to carry out a specific task. The work done in an Interactor should be independent of any UI.


*Entity

If you are using Core Data, you will want your managed objects to remain behind your data layer. Interactors should not work with NSManagedObjects.


*Presenter

Entities are never passed from the Interactor to the Presenter. Instead, simple data structures that have no behavior are passed from the Interactor to the Presenter. This prevents any 'real work' from being done in the Presenter. The Presenter can only prepare the data for display in the View.


*View

The View is passive. It waits for the Presenter to give it content to display; it never asks the Presenter for data. Methods defined for a View should allow a Presenter to communicate at a higher level of abstraction, expressed in terms of its content, and not how that content is to be displayed. The Presenter does not know about the existent of UILabel, UIButton, etc. The Presenter only knows about the content it maintains and when it should be displayed. It is up to the View to determine how the content is displayed. 

The View is an abstract interface, defined in Objective-C with a protocol. A UIViewController or one of its subclasses will implement the View protocol.(这里和MVVM不同,VIPER里View和Interactor双向交互均用interface实现,而MVVM里View可以引用ViewModel,user input不需要protocol实现,或者直接使用ReactiveCocoa)

The boundary between the View and the Presenter is also a great place for ReactiveCocoa. In this example, the view controller could also provide methods to return signals that represent button actions. This would allow the Presenter to easily respond to those signals without breaking separation of responsibilities. 


*Routes

Routes from one screen to another are defined in the wireframes created by an interaction designer. In VIPER, the responsibility for Routing is shared between two objects: the Presenter, and the wireframe. A wireframe object owns the UIWindow, UINavigationController, UIViewController, etc. It is responsible for creating a View/View Controller and installing it in the window.

Since the Presenter contains the logic to react to user inputs, it is the Presenter that knows when to navigate to another screen, and which screen to navigate to. Meanwhile, the wireframe knows how to navigate. So, the Presenter will use the wireframe to perform the navigation. Together, they describe a route from one screen to the next.

The app is using a custom view controller transition to present the add view controller. Since the wireframe is responsible for performing the transition, it becomes the transitioning delegate for the add view controller and can return the appropriate transition animations. 


*It's typically up to the Interactor to initiate a network operation, but it won't handle the networking code directly. It will ask a dependency, like a network manager or API client. The Interactor may have to aggregate data from multiple sources to provide the information needed to fulfill a use case. Then it's up to the Presenter to take the data returned by the Interactor and format it for presentation. 


*A module's Presenter usually implements the module interface. When another module wants to present this one, its Presenter will implement the module delegate protocol, so that it knows what the module did while it was presented. 


*Test部分内容以后再看。


*VIPER is an architecture based on the Single Responsibility Principle.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值