3

I have a very small Xcode project with several view controllers. I found myself duplicating the following methods in them:

- (void)postTip:(NSString *)message {
    [self postInfoAlertWithTitle:@"Tip" andMessage:message andAction:@"Got it!"];
}

- (void)postInfoAlertWithTitle:(NSString *)title andMessage:(NSString *)message andAction:(NSString *)action {
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:action style:UIAlertActionStyleDefault handler:nil]];
    [self presentViewController:alert animated:YES completion:nil];
}

Of course, this got me thinking about how I might remove (or at least reduce) the duplicated code.

The obvious answer is to put the desired behaviour in a parent class, and then have my view controllers inherit from that class. However, some of the view controllers are of type UICollectionViewController and some are of type UITableViewController, and I don't know how to preserve their collection and table flavours, respectively, in the event that I have them inherit from a hypothetical MyViewController is-a UIViewController.

So I did a bit of research and took a look at protocols. Initially, this seemed like a good fit, except that you can't provide a default implementation for methods declared in a protocol, which is basically what I would want.

Finally, and with much hesitation and self-loathing, I considered placing the behaviour in my AppDelegate class, with an additional parameter to facilitate presenting the alerts:

- (void)postTip:(NSString *)message toController:(UIViewController *)controller;
- (void)postInfoAlertWithTitle:(NSString *)title andMessage:(NSString *)message andAction:(NSString *)action toController:(UIViewController *)controller;

A call in a given view controller looks like this:

[self.appDelegate postTip:@"Git gud!" toController:self];

Et voila! The behaviour I want, where I want it, and all I have to do it get an instance of the AppDelegate! But...that doesn't sit well with me. It seems...smelly. In addition, there is still some duplication, i.e., declaring and initializing a private appDelegate property, which I have taken care to do rather than calling (AppDelegate *)[[UIApplication sharedApplication] delegate] wherever I need it so that:

  • I may specify "weak" and avoid possible retain cycles
  • I only take one pointer to the AppDelegate (hurray for premature optimization >.<)

Is it considered acceptable to use AppDelegate as a repository for app-wide behaviour such as utility methods, and if so, am I being unnecessarily paranoid about the implementation re: using a property? (If not, what are my options?)

kuipersn
  • 180
  • 9
  • 4
    Create a Category on `UIViewController`, that will have the methods (the initial ones, not the AppDelegate ones). `UICollectionViewController` and `UITableViewController` inherits from `UIViewController`, so that should be okay. Avoid using your AppDelegate like this adding too much info unrelated. What you are using in reality is the fact's it's a singleton (that can be made by yourself). – Larme May 03 '17 at 16:38
  • 1
    You could create a "TipHelper" class and use an instance (or shared instance) of that in your various controllers. Since inheritance isn't an easy option for you, composition is another available strategy. – Phillip Mills May 03 '17 at 16:39
  • Larme - Ahhh, I did not know about categories! Very interesting, and, I think, just the ticket! – kuipersn May 03 '17 at 16:53
  • Phillip - Hmm, yes that is also a good suggestion: basically swap out the instances of AppDelegate with a helper class. I should have mentioned that I had thought of that earlier, but decided against it because I ended up implementing parameterized init methods for each controller, to keep things decoupled (whereas there is no sense trying to decouple from AppDelegate). Given the even greater amount of essentially "duplicated" code with the new init methods, I figured it was easier to use AppDelegate. The argument could be made either way, but I agree that your suggestion is cleaner. – kuipersn May 03 '17 at 16:57
  • As you said you have `BaseViewController` similarly you can create `BaseCollectionViewController` and `BaseTableViewController` inheriting `BaseViewController` not `UIViewController` and use both like you used for UIViewController all the methods will follow, this is more cleaner way to do this rather creating category as it allows you to define properties too, and avoid `AppDelegate` as it doesn't destroy itself, so you might end up eating memory. – iphonic May 03 '17 at 17:44

2 Answers2

1

Definitely use a category by creating .h and .m files

UIViewController+InfoAlert.h

@interface UIViewController (InfoAlert)

- (void)postInfoAlertWithTitle:(NSString *)title andMessage:(NSString *)message andAction:(NSString *)action;

@end

UIViewController+InfoAlert.m

#import "UIViewController+InfoAlert.h"

@implementation UIViewController (InfoAlert)

- (void)postInfoAlertWithTitle:(NSString *)title andMessage:(NSString *)message andAction:(NSString *)action {
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:action style:UIAlertActionStyleDefault handler:nil]];
    [self presentViewController:alert animated:YES completion:nil];
}

@end

then just import your UIViewController+InfoAlert.h where you want to use your new postInfoAlertWithTitle method

0

It seems to me that categories were intended for exactly this kind of situation -- though as others have pointed out in the comments, there are additional options -- and this is what I have done.

To view Apple's category documentation, see this page. To add a category in Xcode, see this page.

Community
  • 1
  • 1
kuipersn
  • 180
  • 9