18

Often times I initialize my model class variables in my AppDelegate so they can be used by different ViewControllers without passing their instance across class files. However, every time I import AppDelegate into a .m file to access these variable's data I feel like I'm doing some wrong.

Is this the the correct way for accessing these variables or should I be doing something differently?

EDIT: My problem isn't how to access the variables. I currently use this line of code to get my appDelegate instance:

id appDelegate = [[UIApplication sharedApplication] delegate];

Conceptually, I want to know if this is the accepted way to interact with an application's model classes. It seems, to me, that an application's AppDelegate manages the application overall. So it seems counterintuitive to import this class into other classes further down an application's class chain.

Caleb
  • 124,013
  • 19
  • 183
  • 272
J Max
  • 2,371
  • 2
  • 25
  • 42
  • 1
    +1 Good question and totally correct intuition - trust yourself and change that app-delegate __mutilation__ habit :D – Till Dec 07 '11 at 20:27

7 Answers7

15

Is this the the correct way for accessing these variables or should I be doing something differently?

You'll find that different people have different opinions about this. The style that I prefer is to have the app delegate pass the necessary information to the first view controller, and have that view controller pass it on to whatever view controllers it creates, and so on. The main reason for this is that it prevents child view controllers from depending on things that they have no business knowing about.

If you have some detail editor, for example, you want to be able to pass that editor exactly what it needs to do its work. If you give it that information, the editor is completely flexible -- it'll edit any information that you give it. If the editor knows that it should get its data from some external object, like the app delegate, then it loses some degree of flexibility -- it can only get data from the thing that it knows about.

So, it's fine to set up your data model in the app delegate. But when it comes to providing access to the model, think: tell, don't ask. That is, have the app delegate tell the first view controller what model object to use, and have that controller tell the next one, and so on. If you have to ask, you have to know who to ask, and that's where the dependencies start heading in the wrong direction.

every time I import AppDelegate into a .m file to access these variable's data I feel like I'm doing some wrong.

Trust that instinct. Think about why it feels wrong.

Caleb
  • 124,013
  • 19
  • 183
  • 272
  • 3
    +1 Nice way of explaining the destructive nature of dependency buildup. – Till Dec 07 '11 at 20:23
  • Generally good advice for models in view controllers, but sometimes there's global context information about the _state_ of the app that's not in the model and where it just gets unnecessarily verbose if you pass it from VC to VC to VC. For these cases I find LavaSlider's answer to below to be the most elegant solution. – Rhubarb Nov 02 '12 at 13:20
  • I often use this to get the managedObjectContext residing in the Appdelegate. What can I do to avoid this? – nr5 Apr 02 '17 at 11:26
9

I agree that sometimes it seems like the AppDelegate is the logical place to put things that you only want implemented once but may need from several places. Creating a singleton for each one is fine, if those things are complicated, but it does create a lot of additional files and confusion to the project. I also agree with the majority of answers here, that building dependencies on the AppDelegate is a really poor design.

I think the best solution is to create a Protocol! Then put an IBOutlet to a property to do what you need to have done in each of the controllers that need the function. Protocols are the standard objective-C way to uncouple classes.

So, as an example, maybe I have a database URL that I may need from a bunch of places. Probably the best way would be to set a property with it each step along the way. But in some situations that may be cumbersome because of using a stock controller and not wanting to subclass it. Here is my solution:

Create file: MainDatabasePovider.h

#import <Foundation/Foundation.h>
@protocol MainDatabaseProvider <NSObject>
@required
@property (nonatomic, readonly) NSURL *applicationDocumentsDirectory;
@property (nonatomic, weak) NSURL *mainDatabase;
@end

Now "anyone" (i.e., any class) that says it implements the MainDatabaseProvder protocol is guaranteed to provide the two methods above. This could be the AppDelegate or ANY object.

Now if I want my AppDelegate to provide the information I change the AppDelegate.h file to have:

#import "MainDatabaseProvider.h"

@interface AppDelegate : UIResponder <UIApplicationDelegate, MainDatabaseProvider>
@property (strong, nonatomic) UIWindow *window;
@end

(The only change is to add the MainDatabaseProvider protocol to the @inteface line, again this could be done to ANY class that you want to provide the function).

In my AppDelegate.m file I have to write the two methods...

@implementation AppDelegate
      ...
@synthesize mainDatabase = _mainDatabase;
      ...
- (NSURL *) applicationDocumentsDirectory {
    return [[[NSFileManager defaultManager] URLsForDirectory: NSDocumentDirectory inDomains: NSUserDomainMask] lastObject];
}
- (void) setMainDatabase: (NSURL *) mainDatabase {
    if( _mainDatabase != mainDatabase ) {
        _mainDatabase = mainDatabase;
    }
}
- (NSURL *) mainDatabase {
    if( !_mainDatabase ) {
        NSURL *docURL = self.applicationDocumentsDirectory;
        self.mainDatabase = [docURL URLByAppendingPathComponent: @"My Great Database"];
    }
    return _mainDatabase;
}
      ...
@end

Now in my controllers or other classes that have need of getting the MainDatabase I add the following:

In their .h files:

#import "MainDatabaseProvider.h"
      ...
@interface myGreatViewController: UIViewController
@property (nonatomic, weak) IBOutlet id <MainDatabaseProvider> mainDatabaseProvider;
      ...
@end

This property can be set in what was formerly known as InterfaceBuilder by control-dragging or can be set in code in prepareForSegue or I like to provide a custom getter that default it to the AppDelegate in case I am lazy or forgetful and don't do either of the above. In their .m file is would look like:

@implementation myGreatViewController
@synthesize mainDatabaseProvider = _mainDatabaseProvider;
      ...
- (id <MainDatabaseProvider>) mainDatabaseProvider {
    id appDelegate = [[UIApplication sharedApplication] delegate];
    if( !_mainDatabaseProvider && [appDelegate conformsToProtocol: @protocol(MainDatabaseProvider)] )
        return appDelegate;
    return _mainDatabaseProvider;
}
// To get the database URL you would just do something like...
- (void) viewWillAppear: (BOOL) animated {
    NSLog( @"In %s the mainDatabaseProvider says the main database is \"%@\"", __func__, self.mainDatabaseProvider.mainDatabase.path );
}

Now the mainDatabaseProvider can be ANY object. I have the ability to set it in InterfaceBuilder or my StoryBoard (although I don't really think of this is a user-interface item so I typically wouldn't but it is pretty typical to do it that way), I can set it in code outside my controller before it gets loaded in prepareForSegue:sender: or tableView:didSelectRowAtIndexPath:. And if I don't set it at all it will default to the AppDelegate if I have configured it properly.

I can even put some safe guards in for when I forget to do things in my old age by changing the getter listed above to help me out with something like:

- (id <MainDatabaseProvider>) mainDatabaseProvider {
    if( !_mainDatabaseProvider ) {
        id appDelegate = [[UIApplication sharedApplication] delegate];
        if( ![appDelegate conformsToProtocol: @protocol(MainDatabaseProvider)] ) {
            NSLog( @"Hey!! The mainDatabaseProvider is not set and the AppDelegate does not conform to the MainDatabaseProvider protocol. How do you expect me to figure out where the database is!" );
        } else {
            return appDelegate;
    }
    return _mainDatabaseProvider;
}
LavaSlider
  • 2,494
  • 18
  • 29
  • +1 Great answer. Just the solution I was looking for. What you're suggesting in a nutshell is: 1. it's sometimes desirable to use a global shared context and putting it in the AppDelegate is okay, but 2.use protocols to avoid locking yourself into that or any other specific singleton. In my experience (outside of iOS and obj-c) sometimes that single global context ends up getting upgraded to multiple contexts, requiring massive refactoring - but your solution makes that easy. IBOutlets for use in VC is a nice touch too – Rhubarb Nov 02 '12 at 13:23
  • in that case.. you can have a singleton class implement this protocol.. and then all the classes can ask for it from that singelton right (ie instead of having the app delegate implement it).. there seems to be strong [opinions](http://stackoverflow.com/a/9213624/766570) against having the app delegate act like a model.. +1 for a great answer though – abbood Mar 12 '13 at 14:38
  • also one concern i have is that if any object can implement this protocol.. then you can have different databases sources depending on what object you are asking for it.. can't that be potentially confusing? – abbood Mar 12 '13 at 14:39
3

You should seriously avoid importing the AppDelegate everywhere and you should feel like you are doing something wrong each time you do it (+1 for that). You are essentially creating a Big Ball of Mud and should reconsider your design. If for example you are using CoreData for your models consider a framework such as Magical Panda Active Record to retrieve data. I work on an enterprise application and the AppDelegate.h is only include in AppDelegate.m.

Community
  • 1
  • 1
Joe
  • 56,979
  • 9
  • 128
  • 135
2

I'm importing them too and use it like this:

 AppDelegate *delegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
 [delegate variable];

Another way can be to use a Singleton.

Josh O'Brien
  • 159,210
  • 26
  • 366
  • 455
btype
  • 1,593
  • 19
  • 32
1

Put this method in your AppDelegate class

+ (AppDelegate *)get {
    return (AppDelegate *) [[UIApplication sharedApplication] delegate];
}

And when you need to call your AppDelegate use:

[AppDelegate get];
Bruno Domingues
  • 1,017
  • 9
  • 12
1

That is one way of doing it, yes, however it is not very elegant. Singletons are a way too, yes, however not very elegant :) - and reeealy NOT easy to test your code, if you have to mock out all your singletons. Instead what I probably would do is to have one singleton for a service provider, and ask this service provider for an instance your model provider.

Say, your service provider class is a singleton and you need to access the model for the view user details. I would do it in the following manner:

JMUserDetailModel *myModel = [[[JMServiceProvider sharedInstance] modelProvider] userDetailModel];

This means that you would create a JMServiceProvider class for registering services, and being able to retreive these services. These services act a bit like a singleton, however if you need to unit test your code, then registering a different service that acts in the same way as the original is just a piece of cake.

Hope this answers your question.

EDIT: And read this article: http://martinfowler.com/articles/injection.html - a very good one explaining service oriented architectures as well ...

Moszi
  • 3,236
  • 2
  • 22
  • 20
0

Look in to singletons. They can be a more elegant way to manage global data.

Dancreek
  • 9,524
  • 1
  • 31
  • 34