
Hello. One day we learned that we have to do a True Image for Mac OS. As is usually the case, you need to do it quickly and efficiently, yeah. At once a reasonable question arose, why not just compile a true image for Windows under Mac, because most of the code is already cross-platform, including the interface written in Qt? But we were immediately marked frame:
The interface was decided to make a completely new one, many times simpler than that of a big brother. Also, as a GUI framework, experienced in Poppy cases, the guys from Parallels advised to use native ocoa instead of Qt, and people from another well-known company confirmed the correctness of this decision. We decided not to question their experience.
As a result, it was decided to try to write frontend on Cocoa to the existing code. We did release the product and already
wrote about it on Habré , and today I want to share the architectural and technical details of this process.
Passive view
The basis for the new architecture was to put the Passive View pattern, the original description of which can be
read by Fowler .
The pattern itself is ugly simple. Like the classic MVC / MVP triad, there is a view, a model and a presenter (in another terminology, the controller). The difference with other similar patterns is that the view, as the name implies, is “passive” or, simply, “stupid” - it knows nothing about the model, and the presenter is responsible for the coordination of the model and the view.
')

Why this approach?
- Testability is the biggest plus of this pattern. The view and model are isolated, they know nothing about the outside world, except for the reviewers who subscribe to their changes. The presenter, in turn, receives almost all of his knowledge from the outside through a dependency injection. You can write tests for implementations of the form, you can write tests for the implementation of the model, you can write tests for the correct behavior of logic in the presenter;
- Understandability - all the logic of a particular piece is concentrated in one place - in the presenter, and not spread over the types;
- Reusability and composability - the presenter works with the view and the model through the interfaces, so you can use the same logic, designed into the presenter, in different places of the program.
The components interact as follows: the presenter adjusts the view, subscribes to the events of the view and model, shows the view and processes the events of the model and view:
This pattern by itself does not claim to be the most-most, certain things are still at times more convenient to do, using, say, the MVC-approach, when the data itself pulls the data. For example, this was the way the file browser was made in the restore dialog. Passive View is good where there is no large data flow in the view.Code!
Types and presenters we organized in a hierarchy. The presenter of the main window generates other presenters in the event handlers, and they do their part of the work. Conceptually, it all looks like this:
struct ModelObserver {
Since the deadlines were quite tough, all these injections were very useful to us from the very beginning and allowed us to parallelize the work: we changed the models for stubs and did a full test of the interface behavior while the full-fledged models were being implemented. The caps themselves can be reused for unit tests.
At one point, the abstractness of the models can be said to have saved the project’s end dates when the (correct) decision was made not to reinvent the wheel for several subsystems, but to use all the low- and middle-level logic from True Image for Windows. As a result, models were implemented with varying degrees of thickness by the facades or adapters to the existing logic layer, and both True Image versions received all bonuses relying on it, including in the form of fixing ancient bugs that only came out on the Mac (for example, incorrect or insufficient synchronization is better manifested it is on GCC than on MSVC).
We screw Cocoa
It is worth mentioning how we screwed the native Cocoa into this structure, maybe someone will come in handy. We used Objective-C ++ and ARC, we drew windows in Interface Builder. The process is as follows:
- We make a xib window and its obj-c ++ controller, in most cases we use bindings to control the window state
@interface ViewCocoa : NSWindowController { Observable<ViewObservable>* Callbacks; } @property NSNumber* Something; - (id)initWithObservable:(Observable<ViewObservable>*)callbacks; - (IBAction)OnButtonClicked:(id)sender; @end @implementation ViewCocoa { - (id)initWithObservable:(Observable<ViewObservable>*)callbacks { if (self = [super initWithWindowNibName:@"ViewCocoa"]) { Callbacks = callbacks; } return self; } } - (IBAction)OnButtonClicked:(id)sender {
- Making an obj-c ++ adapter that can already be injected into the presenter
struct ViewCocoaAdapter : View { ViewCocoa* Adaptee = [[ViewCocoa alloc] initWithObservable:this]; virtufal void Show() {
Bonus Command Line Interface
Abstraction and passivity of the form made it possible to make an alternative CLI-interface, which is actively used for our automated tests for Mac. It is very easy to maintain it, because for each species it is enough to implement only one class without any business logic!
struct ViewCli : View { virtual void Show() { for (;;) {
Multithreading
From the very beginning we made one essential assumption - to consider all kinds of thread-safe. This made it possible to significantly simplify the code of presenters. The point is that almost all GUI frameworks have the ability to perform an operation asynchronously in the main thread, and used this:
- for qt, this is QMetaObject :: invokeMethod with Qt :: QueuedConnection, or QCoreApplication :: postEvent with an event operation
- in cocoa, this is dispatch_async + dispatch_get_main_queue, or performSelectorOnMainThread
- cli is just mutex
Prosperity
- Again, testability: unit tests, auto tests ... yes, whatever tests!
- Concentration of logic in well-defined places: in practice, it is really very easy to find and supplement the necessary code;
- Reusability of logic: the same presenter can be made customizable, as a result, the types behave differently and remain at the same time "stupid", and we received almost zero duplication of the code;
- The ability to write logic once
and for centuries under different GUI frameworks, since the main approaches are about the same - event-loop, modal and non-modal windows, and so on.
disadvantages
- Callback hell - either a bunch of methods in the same class, or a bunch of small interfaces and presenters, but in any case, it turns out over time;
- The complexity of the implementation of the pattern with Cocoa. People who saw the code for the first time felt especially about themselves. Indeed, to create a new window, you need to create a C ++ view class, a C ++ viewer class, xib, an Objective-C ++ interface and implementation, an Objective-C ++ adapter - a bunch of entities! Compare with the usual Forts and Controls pattern, for example, according to Qt, where only ui and a full-fledged class window with logic are sufficient. Here you just have to understand what to sacrifice for. Testability and free CLI for us are significant advantages, so this complexity had to suffer. However, as a rule, over time, the number of new windows ceases to grow, giving way to fixing bugs and adding existing code.
General impressions
Free CLI is cool! If you have run auto tests. But if it is adjusted, then it is really cool.
Several times the chosen approach saved from rewriting a lot of code, for example, when designers decided to thoroughly redraw most of the interface. For the most part, changes in the code were limited to only the implementation of the class of the form, and almost all business logic remained intact. With all this, according to my feelings, Passive View is more suitable for small or medium-sized applications - for large applications it seems to me that the advantages of flexibility will not outweigh the lack of expensiveness / complexity of expanding the user interface itself.
And what approaches do you use in your projects?