I wrote an article about migrating our iOS app into frameworks and how the app benefits from such work. This article continues and goes into more detail with the work that was done on the project after migration. I describe how you can elegantly link in-house developed frameworks together and what kind of patterns should be followed to achieve truly decoupled architecture for large scale iOS apps.
Note for my PMs…everything was done in my “product spare time” :)
In the architecture I previously discussed, where everything belongs to a proper framework, where the main app target links all frameworks, where project structure is as decoupled as it can be, raises one important question… What is the best way of linking everything together? The state of the art would be to have the main app target (your app that is linking all frameworks together) to delegate responsibility to a framework and then be notified about framework desires e.g. framework that is currently being used wants to present a ViewController that belongs to another framework. The main app would then be notified about this change and the desired ViewController would be presented.
I hope you can imagine all the advantages of such an approach. First of all, I would like to mention reusability, imagine the scenario that happened to Facebook. Their app split into two (Facebook, Messenger), with this architecture it would take a lot less effort to achieve that so as to support both apps in the same time. Since everything is linked to the App at one place you can also quite easily take the whole framework/feature out and link it to multiple apps. No need to mention, that in a team, each developer can take responsibility over some specific framework, but more on that in the previous article.
The Three Tier Architecture
In the previous article I discussed how you can modularise your app into frameworks. In this article I will build on the work we did in that article by dividing the project into 3 logical tiers that follow some strict rules.
First tier (glue)
The first tier consists of our main app target. It has the AppDelegate object, initialises some of our services, responds to notifications and so on. Besides that, it has our main app coordinator. The coordinator responsibility is to handle transitions in the app. It instantiates coordinators from framework features and passes responsibility to them as its child coordinator. Child coordinators notify their parents (in our codebase the main app coordinator) about actions that they are not responsible for. There is a number of reasons why we take responsibility away from the child coordinator, primarily because we don’t want one child coordinator to depend on another one as you can see on the image above.
Second tier (features)
The second tier has all our features, e.g post a project, management page, chat and so on. Each of these frameworks implement its own coordinator that is being instantiated in the first tier. Ideally, the coordinator should be the only publicly visible object outside of the framework. By having the frameworks coordinator as the only visible object we ensure that is the only way to interact with that feature. The second tier follows a strict rule to not to link any other framework from the second tier. The reason for this is to keep the feature decoupled from others, therefore it can be taken in or out of the project very easily. Nevertheless, the second tier can link any frameworks from the third tier and is being linked by the first tier. With that being said, frameworks in the second tier may need to have some shared dependencies, it is likely that, for example, the feature frameworks will need to access the network or persistence, this is what the third tier is for.
Third tier (core)
The third tier is the core of the application e.g models, networking, persistence, localisation and so on… Frameworks from this tier are linked by the second tier without any restrictions. However, unlike the second tier, frameworks from the third tier can link each other. It would be extremely difficult to avoid this linking on the third tier. For example, your networking framework so as the persistent will want to use models framework… You can think of numerous scenarios like that. A possible solution can be to create a fourth tier. Only shared frameworks for the third tier would be in the fourth tier. It would be probably a good solution, however we found it to be a bit over-engineered.
Here is an example of how the workspace could looks like.
Here is a naive example of how the implementation could look. In the code I am using the term flow instead of the coordinator as based on my opinion it is a more accurate term. Lets start with defining a protocol for our Flow. You can checkout the full demo here.
Now let’s look at the main AppFlow (AppCoordinator).
Finally, implementation of a child flow from one of the frameworks.
The coordinator pattern has been known for a while, there is plenty of documentation to get you inspired. I would like to point out here that I have seen some implementations where the coordinator takes over the model and is fetching the data or doing some other controller/view-model jobs. No need to mention, that the coordinator is designed for app flow, that is it’s job. The methods that the coordinator implements should only take already calculated/downloaded values from whatever is responsible for that in your architecture (view model, view controller…). In other words, stay SOLID ;)
This article describes how you can elegantly link in-house developed frameworks together and what kind of patterns should be followed to achieve truly decoupled architecture on iOS. I have written these two articles because it was uncertain where this setup was going to lead us in the beginning. Luckily, everything looks and works really well. I have already mentioned in the previous article that this architecture is a bit of an overkill for small apps, however might be considered before starting a new large scale project.
If you find these articles useful please comment or give me a clap (…or 50) :)