[iOS] Decouple Enum From Module
Our codebase have so many classes in the same target. We decided to move modules from the target. However we have classes coupled with enum, which is a little challenging.
For example, we have a custom tab controller, in which we represent the tabs by an enum type. This is convenient for us to easily identify which tab we are working on and navigation by enum type is pretty straight-forward. The tab controller looks like this:
// In the app targetenum TabType {
case tab1
case tab2
}protocol TabControllerDelegate {
func didSelectTab(forType: TabType)
}class TabController {
let delegate: TabControllerDelegate
init(delegate: TabControllerDelegate) {
self.delegate = delegate
}
func selectTab(forType type: TabType) {
delegate.didSelectTab(forType: type)
}
}
And our app just use it:
// In the app targetclass ViewController {
lazy var tabController = TabController(delegate: self)
}extension ViewController: TabControllerDelegate {
func didSelectTab(forType type: TabType) {
}
}
All things seem to be good. Nonetheless, the problem occurs when it comes to modularization — that is, we intend to move TabController
to a framework, while the usage stays in the app target. Why we do this? Some merits:
- Modularization prevents
TabController
from being coupled with irrelevant types. - Modularization improves organization of our codebase.
- Modularization reduces the change of long rebuild time because the dependency of
TabController
is limited to the module itself. The change to the app target will not triggerTabController
to be rebuilt.
We may simply move enum TabType
, protocol TabControllerDelegate
, and class TabController
to a framework. Does it sound easy? Not really. Although TabControllerDelegate
and TabController
is generic enough to make a framework, enum TabType
isn’t. The enum
is application specific usage. That is, tab controller should be a component serving any enum
type but not limited to TabType
. If we want to leave enum TabType
to the app target, we need some effort to decouple enum TabType
from TabControllerDelegate
and TabController
.
The savior: generic classes and protocol associatedtype!
// In the framework target TabKitprotocol TabControllerDelegate {
associatedtype T
func didSelectTab(forType: T)
}class TabController<T, Delegate: TabControllerDelegate>
where T == Delegate.T {
let delegate: Delegate
init(delegate: Delegate) {
self.delegate = delegate
}
func selectedTab(forType type: T) {
delegate.didSelectTab(forType: type)
}
}
And we use it in the app target:
// In the app targetimport TabKitenum TabType {
case tab1
case tab2
}class ViewController {
lazy var tabController = TabController(delegate: self)
}extension ViewController: TabControllerDelegate {
func didSelectTab(forType type: TabType) {
}
}
Yes, we decoupled enum
from the class! Hurray!
Hope this tip helps you modularize your codebase. If the tips are helpful, please give me a clap to let me know! :)