One of the most common ugly patterns I see while reviewing iOS code is the overcrowding of the UIAppDelegate with calls for all kinds of services. It all starts with the CoreData stack setup code, then someone adds code to setup an analytics service, short followed by a Push Notification service, InApp Purchase, Crash Reporter, etc…
Before you notice it the AppDelegate gets hundreds of lines of unrelated code, hard to maintain and gets to be a central point of all dependencies. One simple pattern can help us to break this cycle: Delegation.
For each of the services create a controller class that represents that functionality and then delegate the necessary calls from the UIApplicationDelegate to the controller. Let’s see an example with the CoreData code.
Start by creating a Master-Detail Application projet using the XCode and select the CoreData option. You will get an AppDelegate with the following properties and methods:
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext; @property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel; @property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator; - (void)saveContext;
Why do we need these methods to be were? We can be easily be moved them to a singleton class called DataController and then refer to it instead implementing them in the AppDelegate. The interface of this object can look like this:
@interface SEDataController : NSObject @property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext; @property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel; @property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator; + (SEDataController *)sharedInstance; - (void)saveContext; - (NSURL *)applicationDocumentsDirectory;
But this will only solve part of the issue, we still have calls being made on the AppDelegate to the singleton. Instead of flooding the AppDelegate with calls on each delegate method we can make our controllers implement UIApplicationDelegate protocol.
@interface SVEDataController : NSObject @property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext; @property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel; @property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator; + (SVEDataController *)sharedInstance; - (void)saveContext; - (NSURL *)applicationDocumentsDirectory;</pre> and then on the .m file we implement the UIApplicationDelegate methods that make sense for this service: <pre>#pragma mark - UIApplicationDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [self managedObjectContext]; return YES; } - (void)applicationWillTerminate:(UIApplication *)application { [self saveContext]; }
For this methods to work you need to refactor your AppDelegate in a way that it forwards the calls to available services
- (NSArray *) services { static NSArray * _services; static dispatch_once_t _onceTokenServices; dispatch_once(&_onceTokenServices, ^{ _services = @[[SVEDataController sharedInstance]]; }); return _services; } - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { id service; for(service in self.services){ if ([service respondsToSelector:@selector(application:didFinishLaunchingWithOptions:)]){ [service application:application didFinishLaunchingWithOptions:launchOptions]; } } }
As you can see we know have a service array that holds all of our services. These services can them respond to the UIApplicationDelegate selectors that are relevant for their execution.
You can now easily add more services, for more services check the example project for SVEApplicationDelegate in github.
Leave a Reply