MVVM, MVC, VIPER… so many acronyms, which architecture is the best? Let’s talk about the things that matter for good app architecture for iOS.
My name is Krzysztof Zabtocki and I recently joined the New York Times, but most of you might know me from my open source work or Foldify, which is an app I made a while ago. You might also know me from Objective-C Playgrounds. When Apple said you cannot do Playgrounds for Objective-C and you can only do it in Swift, I proved them wrong.
Today, I’m going to talk about something that’s very dear to my heart and something that’s very polarizing for a lot of people: good architecture. How do we even define good architecture? It’s kind of like tabs versus spaces when people go talking about those topics. It always gets heated and it always gets hard.
When I was preparing for this talk, I had lots of concerns in what I’m going to talk about because there are lots of design patterns. There is the SOLID principles, there is The Clean Architecture by Uncle Bob and I could talk about all of those things, but the problem with that is that you can read all of that in the proper design books and I am not as smart as the guys that wrote the The Gang of Fours.
I want to talk more about the practical side of doing iOS development. I was hired many times as a consultant to do code reviews over whole projects and to give suggestions to the teams how to improve their architecture, how to make it better, what things are not looking like they are going the right direction and whether we can improve that going forward.
I’m going to talk about what I consider important, so it’s obviously going to be skewed, I’m also going to talk about why Apple’s sample code is bad, and why they did it. They did a talk this year at WWC and they talked about two design patterns that are very nice, but they completely misuse them, so I’m going to talk about that as well. I’m also going to talk about MVVM, which is an architecture that I use often, and why a lot of people make mistakes when using MVVM and how we can improve that and other architectures in general.
What is a Good Architecture?(2:26)
How do we define a good architecture? There are a couple of traits that I always want from an architecture of a specific application.
I want each object to have a specific, clear role. It’s easy to understand, it’s easy to change and when you go and read the source code, you immediately see whether this is actually fulfilling that single role or whether logic you are about to write would breach it.
I want to have a simple data flow which I can understand, and which I can debug easily if there is a crash or if there’s an error. I don’t want to have to jump across multiple different objects, mutating the same shared resource because that’s always hard to find. If you could have unidirectional data flow, that would be the best because it would allow you to just put a break on one place and see what’s happening with your data. Some architectures allow us to do that.
I want to have architecture that doesn’t depend on a specific framework or service because this, I’ve been programming for over 20 years and this has always led me to a lot of pain when I didn’t have a simple abstraction between the dependencies I use and the code I write. Every time, either a framework died, or a service died I’m going to mention that as well when we talk about similar things.
I want it to be flexible, but I want it to be flexible because it’s simple to understand, it’s simple to change. I don’t want it to be flexible because I have 200 abstract classes and everything is abstracted and nothing is understandable for anyone that joins the project or even when you have to add a new feature, you have to go through a lot of hoops to just to be able to add it. I want flexible because it’s simple, not because it’s over-engineered. In more technical terms, it’s separation of concern which is an obvious design pattern of having a single responsibility and making it clear.
Test-ability is a very beneficial thing to think about when designing architecture. It’s not even about the test itself, which I’ll get to in a second. Unlimited dependencies, which is again the framework dependencies, and being away from them. With separation of concern, we define clear boundaries. An object should usually be responsible for a single role. I am saying usually because sometimes you might breach that role and it might still be a good choice if otherwise you would have too many classes. Sometimes you have classes that the role is to coordinate, so some people would classify it as breaching the SRP, but they’re actually doing a single responsibility which is to communicate between different subsystems.
I think it’s helping when we develop our applications, to test it and to understand it. Test abilities is a huge, huge, good trait. This is a very polarizing issue because most of us are not allowed to write tests, given we work for agencies, it might be the thing that sells. Clients might not be willing to pay for it because they don’t understand it. There are lots of reasons why you wouldn’t write tests, but I ran teams before and I always said to people that contract us, that if you’re hired as a contractor to do the whole app, from the ground up, it’s your choice whether it’s tested or not. If someone hires a builder to build their house, they don’t tell them how to do their job, they hire them because they consider the fact that they are good.
If someone hires me to do the whole app, I’m going to do it to the best of my ability, which for me includes testing. This is because it makes a lot of difference. When I’m talking about architecture, there are very clear benefits of having tests.
First of all, when you’re writing tests with a TDD approach, you write tests first. You are designing the API of your architecture, of your classes, of your managers, upfront, before they actually exist. If you are trying to add a new functionality, you might create a class in your test file, but you don’t have the interface until you’ve written the first test.
You write the first test and you write the function based how you would like to use it. You change the perspective. You don’t write it in a void, in a header file, like you did in Objective-C or an empty function in Swift. The first thing you do is you actually try to call that interface, and that seems like a very simple difference, but it actually makes a huge difference in the way your API looks like. When you’re writing those APIs, you want them to be easy to test, because otherwise, tests are very painful.
This is also the reason why a lot of people avoid testing, because it’s hard. I would wager that if tests are hard to write, it usually is a sign of something being wrong in your architecture approach. If you did tests as a first class citizen and you started with tests, you would make the interface simpler, or more flexible. Being able to test them easily is usually what I strive for because that means I can interchange elements in my system very simply.
Get more development news like this
With red-green-refactor which is the approach you take with TDD, you first write the failing test, then make it compile in the simplest way, make it pass the simplest way and then refactor it. You actually end up with a much cleaner implementation, not just tests, but also implementation.
This is definitely something I would consider doing in every project going forward. And if you have to create a lot of fake objects just to be able to test a particular functionality, usually something is off. You have more of them on role, or your system of dependencies is overcrowded.
Foldify is based on Parse and I still have to migrate it. If you had a thin layer over your model objects in your application, which I actually have, migrating from Parse to another back-end provider is much simpler than if you just used the SDK directly. Not to mention that there is decay.
When I adopted it in the beginning, it was very bad. I couldn’t even download images from their server because they were throwing internal assets in a framework. Having that small layer is always beneficial and it helps you avoid troubles ahead of time.
A couple of years in, it’s good to be able to maintain your project that’s bringing in money, even if you are not actively developing it right now. I’m not working on Foldify actively now, but I work on it because there are some system things I need to work with, but I am not creating new features.
Being able to have architecture that’s going to survive for the next couple of years, is very crucial. It’s not like in agencies, where often you get a project and you have to make it work and then it’s out even though you’re no longer working on it.
I always make the assumption that I would be the one fixing the problems, when you think you are the one that’s going to have to fix all these screw-ups, you kind of do less of them.
What are the signs of the wrong architecture, how we could easily check whether there are some potential problems there?
For example, you could search and count the number of lines you have in files. This is usually a good sign. If you have a view controller in Swift that has 3,000 lines of code, or you have a dedicated test that has 4,000, that’s usually a sign. It doesn’t necessarily mean your architecture is bad, because you could have multiple classes there, and they are just in a single file for convenience or something, but it’s a good indicator of something to check.
I actually had a script for all my Objective-C projects which is also available on GitHub, in the case of bootstrap, that would trigger warnings when I had files that were too long, which would suggest to me, “look at this file and check whether you are breaching some roles there.”
The other thing you can do is you can see if people are using global states and Up Delegate in your project, because if they are, I don’t see a good excuse for doing that, seriously. The other one I could excuse if you had multiple classes, but if you are using Up Delegate to store your global properties, it’s not dependency injection, and container, and it’s not using Singleton write, it’s just a global state, it’s just asking for trouble. You could run a dependency visualizer and see everything is pointing to Up Delegate and there are a lot of classes that are interconnected with a lot of other classes which is not very modular, and not very interchangeable.
You want to avoid those and there is actually a way better version of graphing, now. You can check it out on the GitHub repo.
I want to talk quickly about design patterns. I don’t want to go into the whole SOLID principle because there is lots of articles written about it that are way more technical and I don’t want you to start sleeping here.
What I found over the years, is people treat design patterns as a religion and sometimes they get very invested in a specific pattern and they think they can apply it everywhere. I would say that design pattern is a toolkit you have, it’s a toolkit of a lot of different tools, that when applied in a specific scenario, they are very good.
For example, Singleton is not really a bad pattern when they were created for a specific reason, it’s just how people use those patterns that can end up being wrong. You can use all the best design patterns and you can make a total mess out of your architecture.
You have to have enough experience and enough time invested into development to understand and see where a pattern could be applied. I would always be pragmatic about it and if sometimes you have to follow a specific architecture pattern, and something doesn’t fit, but it would make your code better and more testable, it might be worth considering changing the pattern a bit.
Like Singletons, if I asked people here, they would probably say, this is evil, I don’t use it, or I use it because I have no way around it. There are many reasons to use Singletons, but not very many good ones. The pattern itself is not inherently bad, it was created to deal with resource contention. If you actually only can have a single object because you’re accessing some kind of database or something that only allows you to have one interface, you have a Singleton either way, but it’s not supposed to be used the way most people use it, especially on iOS. People just use it as a global state sync, they just throw everything into Singletons.
A logger is a pretty decent example of something that’s usually a Singleton in an app, but especially with Swift nowadays, you don’t have to expose the fact that this is actually using as Singleton, even if you have a single object because it would make your architecture harder.
The reason that people use it is because it just makes it easier to have that access. If I am logging a message from the application, I don’t want to have to pass logger to every single class in my project because I’m going to need logging at some point or another in every class because I’m want to be able to debug it. I want to log some messages in case I have a crash. I need access to a logger of some kind, and I need to be able to log messages to my system, whether it’s a server, or whether it’s a file, it doesn’t matter. I just need a way to have that information.
Even if you have Singletons, you don’t need to expose it, especially in Swift. I have a simple protocol called Loggable and it has a default implementation that uses a Singleton underneath, but that information is never leaked outside of that file and the logger is never accessed outside of this initial configuration in application controller.
If I have a class, and I adhere to Loggable protocol, I immediately get access to a function called log, and that function is underneath using Singleton, but no one in the app knows about it. I could change it in my tests, I could change the implementation of Loggable, in the default implementation, and I could override it. I could even test whether the logging is correct. Whether the class logs those messages is what I’m interested in.
There is lots of things about Singletons that are misused. Whether you have a Singleton doesn’t need to be exposed.
As another example, I see a lot of people using network managers. Let’s say you want to be able to set an image viewer from a URL, a lot of people use Singletons for that. That is usually because of a deeper problem I’m going to mention very quickly. It’s usually a lack of dependency injection, making it easy to do dependency injection.
If there is a single pattern I would recommend anyone to use and learn, it is Composition and there is a very simple reason why I love that pattern very much. It naturally leads to other good choices, because if you are following Composition right, you will have a code that has a single responsibility, you will have code that is DRY. The moment you get single responsibility, and the moment you have that particular object, it’s already DRY. You’re going to use it DRY, you wouldn’t waste your time, and you already have something that’s completely reusable, so you’re not going to repeat the same code.
It’s a pattern that when followed, gives you so many benefits that are free, it’s not something you have to think about. If you just think about Composition, naturally it would lead into the SRP and DRY principles. This makes the code much easier to test because if I have a small object that I can inject into another object, I could inject a fake instead. I could also, when I’m testing that object, only have to test very little interface.
When you have split stuff up into small objects, there is less variability, so it’s less code pass that you need to test.
As a good example of when this workings well is in games and in game dev. If you’ve seen Unity, you’ve seen some very high 3D graphics games. In Unity, you see the Angry Birds Space game and you see so many different games, completely different games using the same system.
The whole system is based on Composition, it’s based on entity systems which are a pattern that just leverages Composition. You can create an object and you can add a behavior to that object. For example, you create an instance of a monster in your game. That monster starts as an immovable one, and it just fires arrows or whatever, and then, your game designer decides it would be nice if this could actually move and follow the player. You just add another object that has that functionality, you just inject it and that monster starts walking after the player.
Unity is probably the best example I have seen in actual industry and it’s able to have so many different games, looking completely different and having completely different features. Even on the level of complexities, there is a lot of variation, and it’s a single engine. It’s a single engine following a single pattern and this pattern is great and it’s really beneficial.
The other pattern that goes along very well with Composition is dependency injection. Once you have those small objects, why do you want to hardcode them? Why do you want to create a new instance that has hardcoded dependencies if you could just inject it?
You already have objects that do this, there’s no point in having a hard constraint if you could interchange it easily. If Unity doesn’t allow you to just change it on normal objects, you would have to create a class for everything. You would have to have a class for the player, and you would have to have a class for the enemy. Sometimes in Unity, you could just create a single class for the enemy and configure it differently. This goes very well with Composition, it’s a great partner.
You can also test. In tests you can swap different implementations. You could have the same object with a small component change and you could do a completely different job, which is very helpful and it needs less testing, even though you would have the same test coverage. Even Apple recommended this this year, they talked about code injection and that it’s great because it allows you to reuse your code and you don’t have strong dependencies. I’m going to talk about that in a second because they totally messed it up and I would like to mention briefly, a couple of popular architecture patterns.
Popular Architecture Patterns(20:39)
There’s a great article by Bogdan Orlov called “iOS Architecture Patterns”. I’m only selecting three to talk about: MVC, Viper and MVVM. I would like the order to be from the least commonly used to the commonly used, but I know the MVC is the default, so it’s probably by far the most used pattern, even though the Apple way of doing MVC is completely and utterly wrong.
With classical MVC, not the Apple one, it was created a long time ago and is more suited for web. You have a view that is dump, it’s rendered by the controller which just takes the model data for it. When the model changes, the controller creates a new view lens, it’s basically like HTML. Because we work in iOS, we have specific frameworks so it’s not so easy to apply that pattern.
What Apple thinks we do, is we have a controller which mediates between the view model and model and the controller is the least usable part and usually people end up calling it massive view controller because that’s what happens. View controller is very tightly bound to the view life cycle which means that you cannot really separate it clearly. Since view controller is not reusable, your view is not reusable and you end up only with the model being reusable which is a waste of resources.
The responsibilities of the view controller and view are all over the place. People do network, downloads, people do data processing, this is just crazy. If you have ever tried to write tests with that architecture, you know why people hate testing. It’s not the testing that’s wrong, it’s the architecture that you are trying to test is completely, utterly difficult. There is no Composition, there is no injection usually and it’s a mess.
Moving on to Viper, the previous two patterns are not actually architecture patterns for your whole app, they are more like UI patterns and Viper is the first one that kind of thinks about the whole architecture, which is a great pattern. It introduces a couple of new objects.
For example, you have the interactor which is responsible for business logic, it’s coordinating some other services which would use Composition. Then you have Router which is very important and it basically allows you to configure different presentation context for your views and view controller so you can show the same screen from multiple places. Presenter is a class that contains the UI kit independent presentation logic. This is stuff that doesn’t touch UI kit, but it formats links and does stuff like that.
It’s a very good pattern, but it requires a lot of Boilerplate to the point where people write a code generator just to generate new modules. There are two or three implementations on GitHub that code-generate Viper classes because it requires so many, which is probably why a lot of people don’t use it.
The most popular pattern by far, in iOS is MVVM, Model-View-ViewModel. The important thing to consider is, your view controller and the view layer. It would be hard to not view controller as a view layer because it’s so tightly coupled with the life cycle.
This pattern is pretty good because you can test your view models. Without it, the view models could not contain zero UI kit, which means there could be no dependencies of having view controllers loading, and you could test all your business logic.
That test ability from MVC would have 30%, with MVVM you could get to 70% and you could have UI tests for the rest. There is also MVVM without the binding libraries, usually very heavy on Boilerplate. You usually want to have some kind of bindings. You don’t need full FRP, because that will usually end up on the other scale. Using ReactiveCocoa and NRX is great, but it’s also a hard, and there is a steep learning curve for people. I personally like it, but I am fine using just binding for MVVM.
Just having a simple, observable class is going to make it work and it’s going to make it much better than the standard MVC for sure. This is probably the most popular pattern we have, but so many people mess it up. The benefits include test ability. You can test your view model because it doesn’t have UI kit. You don’t have to worry about how you are going to test the view controller, checking if you have to fake those life cycle events, I have to call the getter of the view controller just to be able to load the outlets from the need files. All of these things are gone because your view model is clean, nice and you can test the whole view model interaction.
The only thing that’s left to test is whether the view controller binds. It’s lacking a very serious thing however which is something that Viper had and it’s a router. If you don’t have a router, Apple said dependency injection is great, and they talk about it in MVC pattern. They say it’s great because you can reuse your code, and then they show an example which is a view controller pushing another view controller and injecting those dependencies there.
I have a big problem with that. Let’s say you start with a single view controller, it has one dependency. You inject it wherever you created it, and that’s fine. Let’s say you want to add another view controller, and that view controller needs to monitor data because not every view controller has the same dependencies. What do you do?
You need to add the dependency of the view controller B into the view controller A, and then you want to add another view controller and that happens again. Now, your first view controller is becoming a sink for every other view controller in your app. Usually with the app, we have a lot of navigation, so that happens quite a lot. You will have view controllers that contain lots of dependencies that are completely, utterly unreadable. You look at the source and you don’t know which things are actually needed. I look at your view controller and I see, it uses database, it uses image provider, it uses logger, etc, and it might actually just use a single one. It could actually use none of them.
The first view controller might be a welcome screen, it has no dependencies, it just shows a message. Now, you have to inject everything into it which is the big problem. If you don’t have a router, you will end up in a call like that. This is a good scenario because this is only differentiating between iPad and iPhone. If I have to show the same view controller from two different places in my app and let’s assume that only happens because there is a differentiation between iPad and iPhone, I have to have an
if statement in the view controller itself. That’s basically what Apple suggested, view controller pushing view controllers.
I really don’t like that, because then, when the client comes to me, and we need to change a feature in user settings for example, we would normally show the user settings from the user profile screen, but now we actually want to go there from another screen. You need to write another
if statement, you need to introduce more state so you can verify whether this is shown in that context or another context, which is not very good. It’s a lot of dependencies and it’s making it hard to read and change in the future.
It also gets rids of a lot of benefits we get with MVVM. The benefits we get with MVVM start with testing. This is very hard to test, not only does it use some kind of Singleton to verify whether the application is running on an iPad or not, but it’s also pushing view controllers, so it has multiple dependencies. It has a dependency that the present view controller in the specific class works like it’s supposed to, whether it’s overridden or not, and it’s really hard. I wouldn’t even know how to start, and I do testing.
What can we do about it?(29:33)
We don’t want to have unnecessary dependencies, view models don’t need to know about other view models most of the time, unless they are contained. If you have a list view model and it creates items, that’s fine, that happens, but in other cases, I don’t want that. I don’t want to have pure re-usability, and I don’t want spaghetti code because it just drives me crazy, I hate
if statements. I want testing to be easier, not harder.
To test my VM, I don’t want to have to stop my view controllers, that’s not what I’m after. What I want to do is, I have a simple tests. Tests that are a pleasure to write and are easy to verify whether what they actually do is clear as well. If you have things like that, your test declaration will be hard to read and that’s a problem because tests should be the same quality as your code.
There is a way and I call it MVVM plus, it’s just MVVM with flow coordinators and the pattern was introduced to me first by my friend Jim Robka. He showed me, and I was skeptical on the first, but this approach has been developed by multiple people at the same time pretty much and probably before that as well.
It’s basically a router, but it has a bit more responsibility. There are two articles you can read about it to get more in depth, one is on my blog, merowing.info. It’s improving architectures with flow controllers.
The other is coordinator redux which you can find is on the current blog. It’s very similar to router, but this is what happens if you have multiple view controllers. You have one object that’s responsible for configuring your view controllers and view models if you use MVVM because this could be applied to MVC as well as MVVM. You have bi-directional arrows, but that doesn’t actually mean that your view controllers need to even know that you have that pattern.
The way I like to do it, is I like to use view controller as black boxes, a view controller only exposes an interface that informs me whether something crucial happens. For example, if I had
EditUserSettings view controller, that view controller would have save or cancel triggers, and that’s the only thing I’m interested in.
If that’s the case, the flow coordinator, would just create the view controller in a specific context. If I was showing it from the user profile, it might be configured differently than if I was showing it from some other screen.
Profile is probably better to give as an example. If I wanted to show a profile, usually you show it from the main screen when you go, like in the Snapchat, you go to the left, you get the user profile or something. Then, the design changes and now you have to show the profile when you go to a post and you see someone post and I click on someone’s icon and I want to see that screen as well. It’s a different presentation context, you have different configuration context.
What a flow coordinator would do, is it would have multiple configurable methods that would configure it for specific scenarios. The view controller never knows what the scenario it’s configured with. That allows you to reuse the same view controller in multiple places. You could even use the same view controller as the child view controller in a completely different owner view controller which is very useful. You can use either Delegate for the view controller or you use closures depending on how many triggers there are.
If it’s simple, a closure might do just fine. A flow controller creates, configures VM, and it’s based on context. If I had an image picker, if I want to change my profile picture, I might do it from the settings screen, the other might be when I create a new post and that post is based on an image. I want to use the same code, I already wrote it, I don’t want to have
if statements inside of it, and if I use flow coordinator, I don’t.
I can then present it differently on an iPad or iPhone, or even having some kind of containment. There are many places where you could reuse the same code. It just listens to the VM or VC, depending on your architecture events and reacts by coordinating.
For example, if you present it as a model, you would configure it in the flow coordinator, to just present and on the closure, those cancel or save, you would just do dismiss. That knowledge is completely outside of the view models or the view controllers which is very convenient.
It’s also convenient because it allows you to do injection on demand. Each view controller can define only the dependencies or the view model, so you just create the dependency list there and then when you read your files, your view model, you immediately see what is actually used because you don’t need to have a sink, and you don’t need to pass it to another object; you just use the things that you are caring about. This is great for documentation and it also simplifies testing because otherwise I wouldn’t be sure whether that is used anywhere, without searching it manually.
View model has no knowledge of presentation, usually it doesn’t have the UI kit, you don’t even need the UI images, many times you could use a URL as an identifier. You could use a UI image, but it’s basically no views, and no view controllers ever occurring in your view models.
It’s treated as a black box from testing perspective, which means you can have a very simplistic interface and you just test that. It’s reusable because the same pattern can be used in multiple different places.
To talk on the MVVM, this is the approach I follow currently, but there are a couple of different interesting patterns that are starting to grow in the industry and one of them which I’m very excited about is called ReSwift. ReSwift is a library that implements Redux implementation, it’s coming from a back-end or a web perspective and it’s a uni-directional flow architecture, which means it’s very easy to reason about. You only mutate state using pure functions and there is absolutely nothing simpler to test than pure functions. Input and output, that’s all you’d have to test.
It has a single source of truth. With Redux there is only a single state structure in your application. What I like about this is that it has amazing possibilities for tooling because if you have uni-directional architecture, it’s very easy to attach something at any point of that flow and do modification or have extra functionality.
Let’s say you wanted to log all the mutating functions, that’s very simple with Redux because you just add another function inside of there and everything goes through it. You could build stuff like code reloading, you could reproduce bugs.
Imagine you have a crash that happens in another continent and normally you would have a problem with that if that only occurs very rarely, let’s say it only occurs for 1% of your users and it only happens in some very specific scenarios of data, if the server lagged for example.
This is a big problem. We know how hard it is to reproduce bugs like that, and because everything is going through a single point of truth and single mutation point, you could actually save all the actions that are changing your state, and state is source of all evil. If you recorded that, you’re able to reproduce the actions. If you had a crash, you could reproduce everything up to the point before the crash and then check that last point manually while connected to a debugger and you could see the call stuck and what is exactly happening. I would reckon that is much better than just opening the crash report.
It would also allow you to work remotely better because for example, if you are working on a specific feature, you could load the state where the features should appear and then send it to your coworkers and they could just load that state of the application and then you can iterate it.
If you are familiar with some other things I did, or the Playgrounds, I like code injection, I like programming in real time and I want to do it natively, I don’t want to use web frameworks for that. This pattern works very well with it because the moment you can load the state, it means you can recompile the project and reload the state.
You can program in real time, you could create your views and your features while being on a specific screen, being in a specific scenario which is just amazing. If you didn’t try using code injection before, I highly recommend you try. I’m not even talking Playground level, because Playgrounds are not that fast, but in actual applications.
Code Injection Tools(38:34)
There are two tools for code injection and the best one is the Xcode injection plugin. If you just install that, you can even, in your Swift files, recompile them on fly and many times, especially when working with UI kit, it saves you a crazy amount of time. If you just wanted to change some design, that’s great and I saved so many thousands of dollars doing that, it’s highly recommended.
This is an architecture that I’m looking forward to. I think they recently released 1.0 version, but I’m not jumping on it yet because this is pretty hard to do. This is coming from a web perspective and in there, MVC, allows you to render the views directly and with the UI kit, we have very imperative programming model.
There are a couple of libraries that are appearing right now that use that pattern. Use the idea of Redux and build on top of it.
There is a library called Render, that was recently in iOS Dev Weekly. It uses the idea of having functional views, and being able to draw the views just by having a functional state. When that grows, this might become very convenient. I’m not a web developer, I don’t really like web, but I like being able to reproduce my bugs and I like being able to work fast and break things and then just work on the things that I really enjoy and by being able to do stuff like that, and that would be allowed very easily. It’s also pretty easy to test.
In the end, a good architecture, it’s such a hard topic. There is no silver bullet. I don’t believe there is as single architecture that can solve all your problems and I think you should always consider things in regards to the use cases and the requirements you get from your clients.
I worked on a press-publishing platform and that platform was built on top of Reflection. It would be even possible with most of these patterns. Patterns aren’t usually created to work around inefficiencies in languages as well.
For example, I also worked on another project that used Reflection and it was a Premier League application and it was based on widgets. The user could see widgets for different kind of information: a comment, a video, a score, etc. What was great about the architecture is they could leverage Objective-C dynamic nature to be able to enable and disable features just by checking the target in the files.
If I wanted to disable a feature, I would just select a bunch of files and I would just un-select the target, compile the project and the feature was gone and that worked both ways. If I just wanted to add a feature, I just added a couple of classes that followed a specific pattern, compiled it and that new feature worked. I could add 10 developers to my project and they could work independently and they never had to touch the core.
Depending on the requirements, your architecture might be completely different from this. Right now, I default to MVVM, but in the future, ReSwift or another architecture might be more suitable. The good thing about programming is we learn every day and my opinions today might be completely different from my opinions in two years. If they are not, I’m gonna be afraid I’m not learning enough.
I like to design stuff so it naturally leads to cleaner code, I do not try to force myself into very rigid scenarios. Just following some simple principles, like using TDD, using Composition and just having those two things would already allow me to have architecture better than projects I did in the last 10 years.
I want to make it simple, I want to make it flexible because it’s simple. I want to follow, “keep it simple, stupid.” I don’t want to have a lot of abstraction.
There is one thing that I always remember when I talk about over-abstracting, I used to work in game dev, I used to create game engines and frameworks and there were a couple of us working on a 3D game engine and me and my friends had completely different approaches.
My friend was doing everything abstract, he would do every possible scenario and every possible configuration. When he was writing collision, he would write collision for everything with everything, every shape with every other shape. He would do all of those things. He released his version of the game engine, it had like six or seven demos of games and there was a game that looked very similar to Pacman, just in 3D and then he got hired.
That was open source, everything was open sourced, he could use it everywhere. He was hired by a company making games for kids and the game he made for them was basically a Pacman 3D, except for the Pacman, that was a fish.
From the gameplay perspective or even the graphics, there were very little changes and he wrote that from scratch after working for two years on a game engine. I would really think that having flexibility because it’s simple and easy to understand, easy to evolve, it’s something that a lot of people miss when they over-engineer stuff and they try to go way, way over on the abstraction side there.
Obviously, there are benefits to abstracting some things, but it’s a game of balance and I think following solid principles is a good start as well. It’s probably the most known pattern when it comes to architecture and you can always read the clean architecture articles which are very good, but also very complex.
Let’s say I take MVVM option with flow controller, is there any good example of a tutorial of source code with the explanation to fully understand it?
There is an article I wrote on merowing.info and you can read the article about flow controllers and the other is the coordinator redux. You can just write the names in Google and it will come up. “Improving Architecture, and it’s Flow Controllers” and “Coordinator Redux.” Those two articles have some samples and they show what you need. If you have any questions, you can always reach me on my Twitter, I’m happy to help. There is no complex application example. Maybe we can open source something. I can ask if I can open source my interview challenges from some companies because I used it there.
When you have these difficult patterns, it’s sometimes harder to do UI transitions and animations; is it difficult to make some customization for this?
The way flow coordinator works, it basically configures your views. It can configure your view to use this specific transition delegate, so it shouldn’t really be much harder than the standard way of doing it. What you get with it is being able to configure it for different use cases. Let’s say you are doing a very nice animation because a part of your upcoming screen was already there, so if you are transitioning from a small image, to a big image, in an article, that was using some kind of custom animation just to make it nice. In a different scenario, you might open that article from a push notification that’s shown on a completely different screen, so you don’t have that nice animation because there is no anchor to use. In that case, you don’t want to have that transition. With flow coordinator you would actually get the benefit, you don’t have to support both individual controllers, it’s delegating that work somewhere else.
By using coordinators, you mean that you don’t take advantage of Storyboards segues?
You could, I did before, in Objective-C I changed how the Storyboards propagation works. My segues were actually firing under a flow coordinator, but it’s a bit hacky. Segues in general are not. I am a fan of using interface builder, I have a talk about behaviors with the concept I created and I use it a lot, but I think segues are leading to bad choices when it comes to how to architecture wrap, so I don’t use them. Although you can with Objective-C, there is also an article in which I guided how to do it in Swift. But there is a way to do it with segues if you really want to, although there are not that many benefits to using segues. If you use segues, if you configure table view cell-click as a segue, you cannot use that table view in another place unless you want to repeat the same design which is, for me, it’s a big loss.
Why do you think Apple hasn’t really evolved the pattern inside of the SDK?
The MVC pattern, was before iOS because the pattern itself is from 80s. It’s a very old pattern and it worked there and using MVC doesn’t necessarily mean that you end up with very bad code because if you use Composition and you use TDD approach, you can actually test a lot of stuff and then you use the controller to coordinate. It’s just harder by default and if you wanted them to change the default pattern right now, that would be a huge change. It would require so much, documentation, and everything else would have to be updated.
At this point, I think they kind of follow the community, they actually approached me and a couple of other developers to ask how to improve their interface builder. They are there, they listen to the community and they see that people use different patterns, but for Apple to be able to say, use MVVM, that would mean that they give us bindings because MVVM without bindings is very problematic. It’s a lot of Boilerplate and I don’t see Apple joining on the FRP bandwagon because this is very complex. It’s hard to explain to people that if you use a pattern like Viper, it’s a very steep learning curve.
MVC is a good start, when you start working on an iOS app, and you can deliver good iOS apps using MVC, and it doesn’t have to be a total mess. It’s just that it usually ends up being a mess when you have to grow projects over the years and you don’t use better patterns or you don’t at least use Composition.
I think it’s just a matter of how accessible this is. MVC is very accessible, it’s very simple to explain. It doesn’t have to be completely right, but it’s very simple to explain. MVVM is simple to explain, but it requires binding to be very enjoyable, and much simpler to use because without bindings, it’s a lot of Boilerplate.
Viper is hard to explain to people, especially new people. You can see that in Swift, they are trying to get more and more developers involved, now they have the Playgrounds for the iPad, so kids can learn, which is amazing and I’m so happy about it.
I think it’s more about making the platform more accessible. If you come from the background of web, maybe ReSwift is something you will jump on immediately because it sounds familiar. It has a lot of familiar concepts, so I think it’s the default to make it accessible to people from everywhere, from different backgrounds and different levels of experience.
A lot of Apple engineers said that examples are not meant to be examples of good architecture, they are just examples of a specific technology and we should not follow in writing those kind of view controllers. I think there is this difference, accessibility and they are not trying to decide on one or the other pattern, keeping it more flexible.
About the content
This talk was delivered live in June 2016 at mDevCamp. The video was transcribed by Realm and is published here with the permission of the conference organizers.