Issue #24
There is Lighter View Controllers, and there is Lighter AppDelegate, too
Since working with iOS, I really like the delegate pattern, in which it helps us defer the decision to another party.
The iOS application delegates its event to AppDelegate, which over time will be a big mess. Usually, the AppDelegate is where you put your root view controller setup, crash tracking, push notification, debugging, … and we just somehow violent the Single Responsibility principle. Moreover, it makes us hard to reason about code in AppDelegate
Service
I like to think of each task in AppDelegate as a service. And the AppDelegate distributes the events into each service via ServiceDispatcher. Simple plain old composition and looping
I tend to have RootService as a place to setup root view controllers
It looks like this
ServiceDispatcher.swift
class ServiceDispatcher : NSObject, UIApplicationDelegate {
let services: [UIApplicationDelegate]
init(services: [UIApplicationDelegate]) {
self.services = services
}
func application(application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
services.forEach { service in
service.application?(application, didFinishLaunchingWithOptions: launchOptions)
}
return true
}
func applicationDidBecomeActive(application: UIApplication) {
services.forEach { service in
service.applicationDidBecomeActive?(application)
}
}
func applicationWillResignActive(application: UIApplication) {
services.forEach { service in
service.applicationWillResignActive?(application)
}
}
func applicationWillEnterForeground(application: UIApplication) {
services.forEach { service in
service.applicationWillEnterForeground?(application)
}
}
func applicationDidEnterBackground(application: UIApplication) {
services.forEach { service in
service.applicationDidEnterBackground?(application)
}
}
}
RootService.swift
class RootService : NSObject, UIApplicationDelegate {
func application(application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
appDelegate().window = UIWindow(frame: UIScreen.mainScreen().bounds)
showHome()
appDelegate().window?.makeKeyAndVisible()
return true
}
}
extension RootService {
func showHome() {
let home = HomeWireframe().makeHome()
let navC = UINavigationController(rootViewController: home!)
appDelegate().window?.rootViewController = navC
}
}
extension RootService {
func appDelegate() -> AppDelegate {
return UIApplication.sharedApplication().delegate as! AppDelegate
}
}
AppDelegate.swift
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let serviceDispatcher = ServiceDispatcher(services: [RootService()])
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
serviceDispatcher.application(application, didFinishLaunchingWithOptions: launchOptions)
return true
}
func applicationWillResignActive(application: UIApplication) {
serviceDispatcher.applicationWillResignActive(application)
}
func applicationDidEnterBackground(application: UIApplication) {
serviceDispatcher.applicationDidEnterBackground(application)
}
func applicationWillEnterForeground(application: UIApplication) {
serviceDispatcher.applicationWillEnterForeground(application)
}
func applicationDidBecomeActive(application: UIApplication) {
serviceDispatcher.applicationDidBecomeActive(application)
}
}
I have more services like DebugService, PushNotificationService, CrashTrackingService, …
The downside to this approach is that in real life, there will be dependencies between those services, like that UserService must be called before RootService? In this case, I have to use comment to explain why I have that decision, which is hard for newcomers to understand at first. Take a look at How to Move Bootstrapping Code Out of AppDelegate for how dependencies are managed
JSDecoupledAppDelegate comes with another approach, in which service events are named according to the functions, like appStateDelegate
, appDefaultOrientationDelegate
, watchInteractionDelegate
, …
But for me, Service and ServiceDispatcher suit my need