This work describes a solution to a costly problem that
surfaces in software product engineering on Objective C technology stack.
Particularly, it emerges when designing a software product targeting both OS X
and iOS where a significant part of the functionality is common for both
platforms. This brings in the question of how much code can be reused between
the two implementations.
A multitude of concerns need to be probed and the solution
domain can easily be a victim of combinatorial explosion. Like in many other
cases, analysis in terms of design patterns can put things in perspective. Both
Cocoa and Cocoa Touch (standard frameworks for application development in OS X
and iOS respectively) highly encourage embracement of MVC as a design pattern.
It is even a necessity for utilizing certain parts of the two frameworks.
Therefore, it is wise to keep our solution inside the MVC design paradigm. Our
exploration to the solution starts with a close examination of MVC and the role
played by each component in the design pattern.
Controllers in both Cocoa and Cocoa Touch are responsible
for managing views and responding to user actions, embracing Strategy pattern.
They are tightly bound to the views they control and therefore are hardly useful outside the
context of that particular view. At the same time views and controllers in OS X
(based on NSView and NSViewController respectively) are disparate from views
and controllers in iOS (based on UIView and UIViewController respectively).
This is mostly due to differences between the two platforms. It inevitably confines cross-platform reusable
space to model classes. On the other hand, it is not very unlikely to have
certain views in the product that are supposed to appear and respond to user
events in similar ways in the two platforms. Failure to exploit this cleverly
in the architecture can lead to code replication causing less maintainability. It
will also disrupt the homogeneity in user interface semantics of the
application between the two platforms. Thereby strict adherence to traditional
MVC hinders the architect from harnessing the power of this domain artifact.
Strict MVC is not the only blockade for reusing user
interface semantics between OS X and iOS. There is another prominent disparity
between Cocoa and Cocoa Touch in terms of the degree of support to implement
Observer pattern between models and views. Cocoa supports configuring bidirectional
bindings between views and models straightaway through Interface Builder
(designer tool used for OS X and iOS application development) whereas Cocoa
Touch does not. iOS developer has to craft a bidirectional binding solution
atop framework-provided Key Value Observing and Key Value Coding patterns.
The above reasons compel the solution space for implementing
generic user interface logic to stay outside of framework-provided, and hence
platform-specific view and controller base classes. However, user interface
logic does not naturally fit inside models in MVC too. A diligent analysis
would make it clear that the need here is a cluster of classes where each one
stands as an abstraction of a particular view. This relates closely to the idea
of view models in MVVM design pattern
which is popular in Windows application development. In MVVM paradigm, a view model
is a container for data that are shown and manipulated in a view along with
behavioral logic that rules user interaction with the view. A view model is not supposed to know about specific
view instances while it is the view that hooks into the view model.
As a means of encapsulating generic user interface logic we
experimented implementing view models to supplement the MVC design. Our
architecture comprised models, views, controllers and view models . Solution constituents
are as follows.
- Models encapsulating business logic, data logic, etc. are implemented by following the typical MVC notion. These classes are reused between OS X and iOS.
- View models implement the common parts of user interface. Each view model contains data shown in its intended view and user interaction behaviors. View models are also shared between the implementations for two platforms.
- Views in OS X implementation bind their data to corresponding view model data members. This ensures that changes in views are reflected in view models and vice versa without any glue code.
- Views in iOS implementation hook into the same view models as used by OS X. They make use of user interaction logic in view models in a similar way. However, data binding with view models needs to be implemented explicitly. The glue code is implemented in controllers using Key Value Observing and Key Value Coding.
- In both OS X and iOS implementations controller classes are responsible for managing views. Since bidirectional bindings between views and models are configured via Interface Builder, controllers in OS X implementation are very lightweight. Controllers in iOS, on the other hand, require glue code that implements bidirectional bindings in addition to view management code.
- Similar to MVVM pattern, views do not directly associate with models. Any data contained in models that require being accessed by views are exposed to views through view models.
This scheme enables developers to implement common user
interface logic in view models that are shared between the two implementations.
In addition to the key benefit of improved code reuse, it delivers numerous
other advantages too.
- Clearly separating common user interface logic into view models enhances readability and maintainability by making it easier to distinguish between common user interface behavior and platform-specific nuances.
- Keeping user interface logic in view models improves testability compared to keeping it in controllers because testing the later needs view mocks while the former does not.
Taking the best advantage of this design requires adherence
to few disciplines.
- Since the view models are to be shared between the implementations for two platforms it is necessary to limit the use of framework classes inside view model code only to the common ones between Cocoa and Cocoa Touch. Our use of the design in practical projects proves that this is not a prohibitive limitation since all platform-specific code usually lands in either views or controllers.
- When there is a need for binding a view to a bunch of data members in a model object, it is advisable to expose the model object through the view model (using composition) rather than implementing proxy data members inside the view model. Later approach would generate lot of overhead to keep data members in view model and their counterparts inside model object in sync.
View models generally sound alien to Objective C
world. However, our experimentation with the design explained above yielded clear
evidence that view models, once employed in combination with MVC in
architectures for products targeting both OS X and iOS, carry enormous
potential to boost code reuse in addition to several other advantages. We
tested it in a product that was developed for both Mac and iPad. We believe
that it will serve as a generic design pattern for similar cases.