Xcode at scale - Refactor AppCore - we really need it?
This is the seventh step of a serie named iOS at Scale based on the next steps:
- Introduction
- Xcode at Scale
- Refactor MarvelClient - split client from logic
- Refactor DataProvidersKit - applying Iversion of Control
- Refactor DetailKit - Single data flow, states, type erasure and more
- Refactor Navigator - Back to simplest
- Refactor AppCore - we really need it?
- Dependency Injection - flexible and composable
- Extra ball: Cache made easy
- Extra ball: Introducing to Promises 1
- Extra ball: Introducing to Promises 2
- Final conclusions
The final module has come. It’s time to see what AppCore
module it’s giving us and why. Lets move to the step-5 on git.
One of the thoughts about this module, is that I don’t understand why this module it’s here. I’m fully agree about make our applications by modules, to limit those modules responsabilities, hide how that module works with access modifiers of our codes, and expose only the components that we need to expose. This is the most usual thing in other languages like Java
. But before of create a Module, we must stop and think, We really need it? Becuase not always more it’s better.
I guess the app target must have inside the AppDelegate
and must be responsible of building the app. Talking about modularized apps, I have mixed feelings about Feature Freamework, about his opportunity cost, but I understand that on big teams, they limit very well what pieces of code has to touch each team, and that could and make up for the complexity it adds (note that with Tuist or other project generators this complixity it’s very reduced). But, back to the AppCore
module, that module it’s doing what our app module has to do. Why we should have this module instead move the code the app directly?
AppCore it’s an extra complexity layer that should be removed. Remember to keep all things as simple as possible.
Refactor the app core module
The first thing to do, it’s to move all the code inside AppCore
, to the app target. (commit: d30ebd9
)
Then, we must remove the Module/AppCore
folder, and updates the dependencies of our app Manifest
. (commit: aa460e5
)
With those simple steps, we keep our app compiling, and we’ve already removed an extra Module, that wasn’t giving us anything.
Not let’s going deeper inside the app target.
Refactor the app target
One of the points that we should keep in mind when developping, it’s to maintain the code as easy as possible. This is not an idea of mine, it’s what KISS principle says. I guess this principle is one of the most forgotten by us as developers, we always try to think on the future of the system what we’re building, and some times we make hard things when it doesn’t has reasons to be.
Let’s see the AppDelegate
. In this case, we here are doing exactly that, premature optimizations. We’ve built an AppLaunchCoordinator
, to keep the code inside AppDelegate
as less as possible, while all the methods on the AppLaunchCoordinator are with a comment, and should be removed.
Also notice that we don’t gain anything removing all the code on our AppDelegate, but having all that code inside other class like the AppLaunchCoordinator
has. This is only move the complexity of one class, to another, but not reduces it, backwards, it adds an extra complexity layer. So take this as a bad practice.
So, the first step to do it’s remove the AppLaunchCoordinator
. If inside our application, our AppDelegate
is really simple, as this is, keep the code inside the app delegate. Then, if we discover that our app delegate’s grows, I suggegst you to do one of the three exposed methods here Refactor Massive AppDelegate. Take the one that better fits on your needs.
Now, our app delegate looks like: (commit: 51c4bb9
)
It’s time to fix the dependency graph. (commit: 29d2c8e
)
final class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var navigator: Navigator?
public func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
setUpAppDelegateDependencies()
setUpWindow()
self.navigator = AppCoreKitAssembly.current.navigator
navigator?.handle(navigation: .root(.list(assembly: AppCoreKitAssembly.current.charactersListKit)))
return true
}
}
private extension AppDelegate {
func setUpAppDelegateDependencies() {
if AppCoreKitAssembly.current == nil {
AppCoreKitAssembly.current = AppCoreKitAssembly()
}
}
func setUpWindow() {
window = AppCoreKitAssembly.window
window!.frame = UIScreen.main.bounds
}
}
Now, we can make this even simpler.
Like swift static lets are lazy, let’s make AppCoreKitAssembly
being a swift singleton. Let’s create a private init
and then make the current instanciate himself. (commit: 42a7e3f
)
Then, we can remove the setUpAppDelegateDependencies
on the AppDelegate
. And we end up with an AppDelegate like this: (commit: a5f47e6
)
final class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var navigator: Navigator?
public func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
self.window = AppCoreKitAssembly.current.window
self.navigator = AppCoreKitAssembly.current.navigator
navigator?.handle(navigation: .root(.list(assembly: AppCoreKitAssembly.current.charactersListKit)))
return true
}
}
Conclusions
This chapter has been short. But what we’ve done?
- We’ve removed the
AppCore
extra module that wasn’t giving us nothing more complexity. - We’ve removed extra complexity inside the
AppDelegate
. - Removed the
AppLaunchCoordinator
layer, showing up that some times, more layers is not better code. More if these layers are simple forwards from one site to other. - Fix the bad singleton creation making our
AppDelegate
even simpler.
We should question always to ourselves when we’re going to add a new layer or new component, if it give us something. Not always have more layers it’s meaning that our code it’s more abstrated or decoupled. Each layer we add to our code it’s adding complexity and more code to maintain and that can make our code hard to refactor in the future.
Steps
- Introduction
- Xcode at Scale
- Refactor MarvelClient - split client from logic
- Refactor DataProvidersKit - applying Iversion of Control
- Refactor DetailKit - Single data flow, states, type erasure and more
- Refactor Navigator - Back to simplest
- Refactor AppCore - we really need it?
- Dependency Injection - flexible and composable
- Extra ball: Cache made easy
- Extra ball: Introducing to Promises 1
- Extra ball: Introducing to Promises 2
- Final conclusions