App Architecture and Object Composition in Swift
Object composition is the core concept of Object-Oriented programming. Objects can contain other objects, sometimes creating complex hierarchies.
In this article I would like to show how to design such architecture. On a real-life example, you will see that working with complex hierarchy is simple, while objects and hierarchies can be reusable.
Object-Oriented design process involves planning of how objects connect and interact to create a part or a complete system. We all used to Model-View-Controller architecture, that defines the controller object that contains the view and the model. Together this composition creates the backbone for an app.
The controller is a composite object that has a view and a model. This can be generalized to “has-a” relationship:
Object is a composite type — has an object of the
Things become interesting with recursive types that form a tree structure.
Object class contains the array of
Object type children forming a tree. We call objects in a tree nodes, and a node without children — leaf.
Recursive composition is simple and can be used to represent any potentially complex, hierarchical structure.
The composite pattern is a structural pattern described in Design Patterns: Elements of Reusable Object-Oriented Software book. It describes how to build a class hierarchy made up of classes for two kinds of objects: primitive and composite.
Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.
The composite pattern defines next participants:
- Component is an abstract interface. It declares functions and used by a client.
- Leaf implements functions declared by Component interface.
- Composite implements composition. It contains array of child components. Composite implements functions declared by the Component interface by delegating (forwarding) calls to its children.
- Client is the code that uses composition through the Component interface.
We can implement the Composite pattern like so:
Real life implementation may be slightly different. Common variation is when Component, Leaf, and Composite are the same object.
Composition using tree structure is a very powerful pattern with many benefits for the app architecture:
- Break complex task into small components. Components solve small tasks. Composed together in a tree to solve a larger task.
- Interact with a complex structure same way as you would interact with a single instance. A tree of components has the same interface as a single component.
- Single Responsibility principle. Component has responsibility over a single function. It can be domain specific or composition specific (like arranging children in a specific order).
- Reusability. Components are reusable, tree of components can be used in larger hierarchy to solve larger tasks.
- Testability. Because components and trees of components are self contained they can be unit tested.
- Opportunity to apply various design patterns. Tree structure works well with variety of creational, structural, and behavioral design patterns.
- Simple. Last but not least. The composite pattern, in special cases, takes onle one class to implement. Trees and operations on trees are well known to developers.
Too good to be true? Let’s take a look at some examples.
UIView’s hierarchy is probably the most prominent example of the tree structure.
Views are the fundamental building blocks of an app’s user interface. Views can be nested inside other views to create view hierarchies. E.g. we construct complex trees using small
We can group views by function:
UIViewclass. It defines the behaviors that are common to all views, renders content, and handles any interactions with that content.
- Displaying content.
UIButtonand other, used to display domain specific content. This views are typically leafs (
UIImageView)and can be composites (
- Managing subviews.
UICollectionViewand other, used to arrange other views.
UIStackViewis a great example — this view is a non-rendering subclass of
UIView, used only to manage layout.
Starting with iOS 5.0,
UIViewController’s form hierarchy similar to
Apple defines two types of view controllers:
- Displaying content. Content view controller manages a discrete piece of an app’s content. This are your
- Facilitating structure and navigation. Container view controller arranges child view controllers in a specific way.
UISplitViewControllerand others, this controllers define navigation and structure of an app.
Once more we see a tree where leafs implement domain specific functions.
Asynchronous Execution, GCD
Original motivation of the composite pattern, as described in The “Gang of Four” book is graphic application and documents. But application goes beyond — how about something asynchronous?
Common challenge when writing asynchronous code is controlling execution order and completion. Let’s assume we have a task that can perform asynchronous work and report completion using a closure (network operation for instance).
We have a list of tasks to perform and we want to know when all tasks complete.
One solution is to use
DispatchGroup, like so:
This is an opportunity to apply composition. Let’s create a class for a group of tasks that require a single completion handler:
Now all we need to do is provide list of tasks to run:
From here, we can build complex hierarchies of asynchronous tasks solving a large problem using small building blocks. If we want to, we can create ordered composite task class. This classes are simple and reusable.
There is a great session about using composition with NSOperation from WWDC 2015 — Advanced NSOperations.
Fetching Data, Persistance
Another great example of composition is fetching and persisting data. Suppose we want to fetch a model and we have two sources: local and remote. We want to prioritize local store. If model is not there — load it from the network. Here is how we can organize them using composition:
CompositeStore iterates over the list of stores till result model is fetched.
DispatchSemaphore used to wait when every fetch operation completes before proceeding. Because order is defined when instantiating
CompositeStore, it will first try to load model from the local store, and if it fails — from the network.
UI Data Source
One of the most interesting application of composition is data source protocols for
Implementing this composition is challenging, but if you have complex UI based on one of this views — it definitely pays off.
This topic is covered in Advanced User Interfaces with Collection Views WWDC session.
As you can see, composition and recursive structure has wide applicability. Things not covered here include formatting, input validation, observing, data processing, state machines, etc.
The composite pattern is very simple, in some cases implemented with a single class. One thing to remember is to follow the roles. Component defines interface, Leaf implements small functions, and Composite combine everything together.
For more advanced architecture with tree structure — computer science is very helpful. Invest some time in learning basic algorithms on trees. Especially tree traversal: Breadth-first search and Depth-first search. This will pay off in long term.
In the end it boils down to identifying complex problems and breaking into smaller tasks. This is a much harder task, but this is were difference between good and great lies.