3

I am working on a project in which I have a model that is used by several different views and thereby viewcontrollers. These viewcontrollers have no knowledge of each others' existence, nor do they have any relationship to each other. This means that I have a model* in each of the viewcontrollers and when the views are loaded, I allocate the model in each class and make the pointers point at it. Or short: I allocate my model n times in n classes that use it, which I think is a waste of memory (not that I will run out of memory, but I think it's bad practice).

Is there a way in iOS where I (while still maintaining good MVC practice) am able to create and use the same instance of my model? Usually I have been programming in c++ where I would pass a reference to the model to the constructor of each class that should know the model. Example (c++):

// Let to classes know of the same model object
MyModel model;
ControllerA myControllerA(&model);
ControllerB myControllerB(&model);

Instead I do the following in each class that use my model (objective-c):

// ControllerA
model = [[MyModel alloc] init];

// Controller B
model = [[MyModel alloc] init];

I don't want to make all models singleton objects and in this specific project I think using an observer pattern would be an overkill.

So, my question is: How can i achieve this and is it even possible?

Kasper Munck
  • 4,173
  • 2
  • 27
  • 50

4 Answers4

3

You can write your own initializer that takes a pointer to the Model.

in the .h file of your ControllerA and B

@property(nonatomic,assign)Model* myModel;

-(id)initWithModel:(Model*)model;

in the .m file of your ControllerA and B

@synthesize myModel;

-(id)initWithModel:(Model*)model{
    self = [super init];

    if(self){
        self.mymodel = model;

    }

    return self;
}

EDIT

If you are using the IB you would write your initilizer in the following way:

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil model:(Model*)model
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {

        self.mymodel = model;
    }
    return self;
}
Cyprian
  • 9,423
  • 4
  • 39
  • 73
  • Well, maybe there's something I am missing. If I do write an initWithModel: method, when should that be called? In IB I tell my viewA to user controllerA, but I am not calling any init method anywhere? – Kasper Munck Jan 19 '12 at 16:14
  • Depending on how you structure your app, your UIViewController (if it is a root controller) could be initialized by either the AppDelegate when it's loaded or by any other UIViewController like UINavigationController. I don't know how exactly you are creating your app. – Cyprian Jan 19 '12 at 16:18
  • OK, I was not aware of that. I have a number of viewcontrollers which are all root controllers for a navigation controller each. – Kasper Munck Jan 19 '12 at 16:22
  • @Muncken pls, look at the edit I added the initializer explicitly for the UIViewController class that is initialized from the IB – Cyprian Jan 19 '12 at 16:22
  • Sorry, forgot to mention that I am using storyboard. I guess I'd have to call init within the prepareForSegue: method? – Kasper Munck Jan 19 '12 at 16:26
  • @Muncken If you're using storyboards, your view controller will be loaded from the storyboard, so you won't be calling the view controller's initializer yourself. Instead, use -prepareForSegue: in the parent controller to pass the model to the child controller. An easy way to do that is to create a property in the child controller that the parent can just set. – Caleb Jan 19 '12 at 16:41
2

I think, you need to use a singleton pattern

Trivial implementation of witch looks like this

YourClass.h

+ (id)sharedInstance;

YourClass.m:

+(id)singleton {
    static dispatch_once_t pred;
    static MyClass *shared = nil;


    dispatch_once(&pred, ^{
     shared = [[MyClass alloc] init];
    });
    return shared;
}

Good article about it apple reference

DaveShaw
  • 52,123
  • 16
  • 112
  • 141
orsi
  • 159
  • 10
  • 1
    I am aware of the singleton, thanks anyway. The thing is that I consider it bad practice to make my entire model layer singletons and thereby global to the rest of the system. – Kasper Munck Jan 19 '12 at 16:15
1

You've got the right idea: provide the model to the view controllers that need it when you create them; don't make go looking to a singleton or other global for the information they need.

Since you asked about view controllers, and since those are often instantiated from .xib or storyboard files, you may need to adjust your approach a little. Instead of providing a reference to the model in the initializer, you can simply add a property that stores a reference to the model to each of your view controllers. The object that's responsible for creating a view controller can then provide the model after the controller has been created. For example, your app delegate's -applicationDidFinishLaunchingWithOptions: method will be called after the root view controller is created, and that's a good time to set up the model and point the root view controller at it:

-applicationDidFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // set up the model
    self.model = [[MyModel alloc] initWithFile:...];

    // get the root controller and give it a pointer to the model
    MyFirstViewController *firstController = self.window.rootViewController;
    firstController.model = model;
}

The root view controller can then pass the model on to other view controllers that it creates. If you have a tab based app, the app delegate might instead pass the model on to all the view controllers under the tab controller. In that case, your tab controller will be the window's root view controller, and you'd get to your view controllers like this:

NSArray *controllers = self.window.rootViewController.viewControllers;

The big advantage here over the singleton approach is that your view controllers don't assume anything about the rest of the app. They'll each use whatever model you give them. That makes your code cleaner, easier to manage, easier to rearrange, and so on.

Caleb
  • 124,013
  • 19
  • 183
  • 272
  • 1
    you caught my point exactly. I have one more question though. Since I am using CoreData and Storyboard, my didFinishLaunchingWithOptions: just returns YES and does nothing else. I am indeed building an app with a tabbar, but I seem unable to reach that tabbarcontroller from within my appDelegate. – Kasper Munck Jan 19 '12 at 16:39
  • @Muncken Your app delegate should have a window property -- I think that's a requirement for using storyboards. The window has a rootViewController property, and that will be set to the tab bar controller. You can then get the various controllers that the tab controller manages. I'll add a snippet above. – Caleb Jan 19 '12 at 16:44
  • Thanks, Caleb. There is, however, one thing that. This tutorial http://www.raywenderlich.com/5138/beginning-storyboards-in-ios-5-part-1 says somewhere at the beginning that didFinishLaunchingWithOptions: should be implemented as I mentioned above. It seems that you disagree with that. Can you explain further? They state that no more is needed when using a storyboard because a UIMainStoryboardFile is set in info.plist. – Kasper Munck Jan 19 '12 at 16:52
  • Before storyboards it was more typical to do a lot of setup in that method -- you had to load or create your view controller, for example. You don't need to do that now, but you can still use it for whatever setup tasks you want. An alternative would be to have your root controller create the model, but you can see how that's difficult in a tab-based app. – Caleb Jan 19 '12 at 17:14
  • I see. Thanks again @Caleb, I'll go with your solution which seems the most elegant to me. – Kasper Munck Jan 19 '12 at 17:21
0

Once you create your model objects, you can store these instances in a global dictionary with a key, in some class which you have only one instance of, i.e. a singleton. When a view controller needs this model, it can ask this singleton to give it the data it needs by providing a dictionary key.

Or you can store your data in coredata, and fetch it from there whenever you need it from a view controller. This way you'll also achieve persistence, if you need it.

aslı
  • 8,740
  • 10
  • 59
  • 80
  • That begs the question: How can several view controllers share the same managed object context? – Caleb Jan 19 '12 at 16:14
  • 1
    My approach is that the model controls the moc. The viewcontrollers request data from the model which then creates the appropriate request, fetches the data and returns it. – Kasper Munck Jan 19 '12 at 16:55