Clean AppDelegate

How can you make sure your AppDelegate has a single responsibility?

TL;DR: Using proxy call forwarding, the AppDelegate can be split into multiple classes. See the link to the full code at the end.

∙   ∙   ∙

The App Delegate has many responsibilites by default including crash reporting, analytics, setting up the CoreData stack, notifications etc.

This breaks the Single Responsibility principle because the AppDelegate handles too many things.
 — Steven Curtis

Having a super clean AppDelegate is an excellent idea. It has always bugged me that everything else follows separation of concerns, but the AppDelegate remains a bloated mess of entry points and OS callbacks.

To avoid this we can implement a form of the strategy pattern, where the behaviours of a class are encapsulated by using interfaces.

I liked the idea so much that I generalized the original implementation by Steven to work for all situations, and cleaned up the AppDelegate.swift file while I was at it. Instead of the usual mess of callback implementations (e.g. application:didFinishLaunching) it simply contains the list of services which are registered to handle callbacks:

class AppDelegate: UIResponder, UIApplicationDelegate {

    @objc public var window: UIWindow?
    
    @objc public let services: [UIApplicationDelegate] = [
        PersistenceService(),
        AnalyticsService(),
        CrashReporterService()
    ]

}

At this point we can implement just the parts of an UIApplicationDelegate that we need for a specific purpose in a separate service class:

// If you want to do something related to analytics in your app , then you should work in this file.
class AnalyticsService: NSObject, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        #if ALPHA
        //register with one id
        #else
        //Register with another one
        #endif
        //Analytics manager starttracking
        return true
    }
}

This, of course, requires a bit of magic behind the scenes to work. So I added a proxy implementation which exploits the fact that the UIApplicationDelegate is an NSObject and simply asks all registered services if they handle the callback

#import "AppServicesSwift-Swift.h"

@implementation AppDelegate (Forward)

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    for (UIResponder<UIApplicationDelegate> *service in self.services) {
        if ([service respondsToSelector: anInvocation.selector]) {
            [anInvocation invokeWithTarget: service];
        }
    }
}

@end

There is a tiny bit of extra housekeeping that needs to be done, specifically methodSignatureForSelector and respondsToSelector also needs to be implemented.

∙   ∙   ∙

Now you can venture out and create your own projects with clean app delegates, and rest easy with the knowledge that even your AppDelegate now has separation of concerns.

This post is based on the original work by Steven Curtis:

Clean AppDelegate

cyborch/CleanAppDelegate