Why you should not fix massive view controller with more massive view controller
I thought VIPER was peak retarded until I saw RIB
Boys and girls, may I present to you, RIB architecture.
Now let’s tear this pile of shit apart.
When designing this framework for Uber, we were adhering to the following principles
- Encourage Cross-Platform Collaboration
If Swift gives you the impression that it gives a shit about how Java does things in Android, you are solely mistaken. The only outcome of this is Java pollution corrupting your iOS code base.
- Minimize Global States and Decisions: Global state changes cause unpredictable behavior and can make it impossible for engineers to know the full impact of their changes. RIBs encourage encapsulating states within a deep hierarchy of well-isolated individual RIBs, thus avoiding global state issues.
Deep hierarchy of inherited states are way worse than global states (which may be well-managed). And this implies heavy usage of global states with usual MVC approach which is not true. You can have local states in view controllers. I also wouldn’t call deep hierarchy of inherited RIBS “well-isolated”. The word you are looking for is “ill-interconnected”.
- Testability and Isolation
You are implying you can’t have good testability and isolation in Apple MVC which again is not the case at all.
- Tooling for Developer Productivity: Adopting non-trivial architectural patterns does not scale beyond small applications without robust tooling. RIBs come with IDE tooling around code generation, static analysis and runtime integrations — all of which improve developer productivity for large and small teams.
You can have non-trivial architectural patterns that scale well beyond small applications. Just because Uber devs can’t design one, does not mean there does not exist one. What this is actually saying is that RIBs need additional tooling for it to scale so as to overcome its architectural shortcomings.
- Open-Closed Principle: Whenever possible, developers should be able to add new features without modifying existing code. This can be seen in a few places when using RIBs. For example, you can attach or build a complex child RIB that requires dependencies from its parent with almost no changes to the parent RIB.
This is where RIB just RIP itself. It straight up tells you that RIB is a). complex; b). requires dependencies from its parent; c). almost no changes, i.e.; so it may introduce changes to the parent RIB!!!
Do you know what a non-trivial architectural patterns that does scale beyond small applications look like?
Does it look like
a). complex child RIB that requires dependencies from its parent with almost no changes to the parent RIB.
b). simple, no inheritance value type, and use mix-ins to add new feature
And guess what? You can add mix-ins by protocol extension in Swift, a POP language. Does Uber employ Android intern to cover as iOS dev?
- Structured around Business Logic: The app’s business logic structure should not need to strictly mirror the structure of the UI. For example, to facilitate animations and view performance, the view hierarchy may want to be shallower than the RIB hierarchy. Or, a single feature RIB may control the appearance of three views that appear at different places in the UI.
Who’s the dumb shit that told you to have business logic structure that strictly mirror the structure of UI? Oh wait, do you think VIPER is a clean architecture too? Are you an Android intern impersonating as a iOS dev???
Hey dumb shit, did it ever occur to you that, maybe, you don’t need hierarchy at all? You can have single value type control state in view controller too! Nothing prevents you from doing that!
- Explicit Contracts: Requirements should be declared with compile-time safe contracts. A class should not compile if its class dependencies and ordering dependencies are not satisfied.
I’m at loss of words. How about no dependencies so there’s no need to check. This is just making up rules to fit your design.
Retarded, Incompetent, Boilerplate (RIB) as an architecture
Imagine this, you are Uber, and you pay 6 figures and stock options to a bunch of so-called “senior iOS developer” with years of experiences and leetcode and shit. And all they do is rip-off Android architecture and call it a day.
“Encourage cross-platform collaboration” they said.
I can smell “massive view controller” is coming next.
First, matured MVC architectures often face the struggles of massive view controllers. For instance, the RequestViewController, which started off as 300 lines of code, is over 3,000 lines today due to handling too many responsibilities: business logic, data manipulation, data verification, networking logic, routing logic, etc. It has become hard to read and modify.
Hey dumb shit, does the word refactor have any meaning to you? What is the hiring criteria of senior iOS developer in Uber? You would think not-writing-massive-view-controller is a minimum requirement.
Imagine you are training junior devs, and ask them to write well-refactored MVC and they reply:
Fuck you man. Uber said MVC is doomed to be massive. Let’s fix it by introducing complex hierarchy, which requires RxSwift to simplify delegation and callbacks from complex dependencies and interactions, which in turn requires extra tooling to detect memory leak which is ported from the tool that detect for RxJava, among other ports from Android. Fuck it, why don’t we just do it exactly like Android.
Senior iOS developers. Boys and girls. Six figures salary and stock options. These guys can’t even write MVC right.
Let’s take a closer look to the supposed “solution” to massive view controller.
Here’s the official complete tutorial of how to do “if logged-in, redirect to either Tic-Tac-Toe or OffGame”:
Or as I would call it:
Never go full retard
What’s the point of having simple view controller, but having such a shit ton of associated files and boilerplate that RIB needs to provide code-gen as a feature.
And RIB has the audacity to claim this is scalable.
Scale my ass. If you need code-gen to scale, you can’t scale.
On the other hand, let’s enumerate a couple of Swift unique features and see how RIB build around them.
- Protocol extension
Nowhere obvious. It is definitely not POP. What’s worse? RIBs can be inherited. Any “senior” iOS developer worth their salt avoids inheritance of control-related states, whereas RIB builds around it.
This comes as no surprise since POP plays a central role in avoiding massive view controller and Uber devs obviously don’t know how to not write massive view controller.
2. Property observer
Nowhere to be seen. Guess it’s too low tech in front of RxSwift.
3. Value type
Not interactor, not router, not view controller, not presenter. It is not central in RIB.
So RIB is basically a port from its Java counterparts.
I’m a dev playing a Android dev disguised as iOS dev
A cascade of failures
There’s a clear path in all the wrong design choices that RIB made. You may view it as a series of patchwork, each attempting to fix the problem caused by the previous one.
Let’s trace it.
- To patch massive view controller
RIB pretends to provide a solution other than creating a couple of sink objects and sink codes to them. But RIB is in fact just creating a couple of sink objects and sink codes to them.
RIB chose to make this Interactor as the ViewModel 2.0, then attempted to fix its problem, namely
The app’s business logic structure should not need to strictly mirror the structure of the UI
For any MVC dev that writes highly compact view controllers, this is “DUH”, and one of the reasons that they never fall to MVVM death-trap in the first place.
This interactor is supposed to handle business logic in a
you can attach or build a complex child RIB that requires dependencies from its parent with almost no changes to the parent RIB
fashion, which is comically retarded.
Take the above tutorial for example, logged-in or not represents different state. So how do you represent different states?
A fucking parent RIB. In-heri-fucking-t-a-n-c-e.
Imagine Redux pulls this kind of shit. How the fuck do you expect it to be scalable this way? (not saying Redux global state is scalable in iOS, but it can work with some modifications)
Wait there is more. You need to remember, for other devs that have to maintain your shit, they don’t fucking have time to figure out what your states are via your inheritance hierarchy, you shouldn’t expose your detail in the first place. Encapsulation. OOP 101.
So you have to introduce another layer of complexity.
2. To patch interactor
It’s called builder. Let’s recap because we already need a recap in RIB. We were considering how to present “logged in or not” as a state. Before we have time to figure it out, we need a general way to wrap it up. If you are half as good as Uber devs claim to be, you’d know it’s hard to do an abstract encapsulation on top of an already-abstract hierarchy of inheritance with listeners as extra dependencies.
You have to make some general assumptions as protocols (think abstract class in Java) and build from there. You can’t provide implementation to general assumptions. This kills POP since your only means of default implantation is via in-heri-fucking-t-a-n-c-e. This is as expected when your iOS devs are Android interns.
You have to construct a builder in order to construct interactor and other shits associated with interactor like presenter.
Recall that you haven’t done shit on the actual problem. All your time are spent on how to get shit done so you can start working on the actual problem.
Also recall that this is a binary state problem. My “massive view controller” is now complete and in testing.
OK!. But at least we can now start working on the problem right?
No, because all the sink objects and interactions and builder in your design. You realize you need outside help to deal with all the delegations, callbacks, escaping closures known as “listeners”, otherwise it’s a fucked up callback hell not indicative of the prestige “senior iOS dev” status.
3. To patch interactions
Easy. Import RxSwift. Nobody can ever question the wisdom and might of Rx paradigms. It also boosts the image of the prestige “senior iOS dev” status.
I’d say it’s actually brilliant, since those who are dumb enough to fall into Rx death trap would never mind hidden complexity and maintainability cost. RxSwift is easy to maintain when we force everyone to use it right? It also hides away the fact that RIB needs RxSwift to dig it out of its own grave.
It is no longer that RIB has flawed design that requires external support when external support is part of RIB solution. I begin to understand their prestige “senior iOS dev” status.
Can we finally get back to the real work at hand?
4. To patch all these boilerplate
You need code-gen for all these boilerplate. Again, it’s not a weakness in RIB design when RIB provides tooling. The RIB solution is now almost* complete. (as in almost* no changes to the parent RIB)
5. To patch all the things missing from Android counterparts
When I was a junior iOS developer, all I need is a networking manager library called Siesta, which is lightweight and built on top of standard URLSession. And the only reason I didn’t write it myself is because I would just write the same thing. (maybe cleaner, but fewer tests)
Nowadays, thanks to RIB. Not only do you need dependency injection library to do dependency injection, that library is also a direct port from Java. You also need RxSwift so you can do callback without using callback.
6. And one more thing
You know what, I haven’t talked about how stupidly retarded this Router design is.
You would think a router is to serve as a dependency injection for view controller navigation.
Typically there’s a login view controller that would handle login related stuff. And no, it doesn’t need to be massive if you do POP or basic refactor right.
RIB, in all its wisdom, decides to route between states. It essentially creates many view controllers for the work that can be done in one. Again, the only guiding principle of RIB is to divide things to smaller sink objects. It actually creates more problem than it solves.
What do you mean, you people?
Look at this list of libraries:
If you like RIBs, check out other related open source projects from our team:
Needle: a compile-time safe Swift dependency injection framework.
Motif: An abstract on top of Dagger offering simpler APIs for nested scopes.
Swift Abstract Class: a light-weight library along with an executable that enables compile-time safe abstract class development for Swift projects.
Swift Common: common libraries used by this set of Swift open source projects.
3 of which are Android ports. And if you look into Needle, it says
Needle encourages hierarchical DI structure and utilizes code generation to ensure compile-time safety. This allows us to develop our apps and make code changes with confidence. If it compiles, it works. In this aspect, Needle is more similar to Dagger for the JVM.
Is your wife’s boyfriend an Android developer?
Can’t you people have some original idea based on Swift features?
This is what you should’ve said:
I don’t know what kind of Android bullshit power play you’re trying to pull here, but Swift, (insert your wife’s boyfriend’s name), is my territory. So whatever you’re thinking, you’d better think again! Otherwise I’m gonna have to head down there and I will rain down an ungodly fucking firestorm upon you!