So it seems to me like there's a Catch-22 situation here. Note the following widely (and smartly) held stances on application architecture:
Apple (and most alpha devs) recommend not using a singleton or accessing the app delegate singleton for retrieving a NSManagedObjectContext. Rigidity, poor design, etc. OK-- I agree!
Iterating over a UITabbarController's child view controllers and giving each child VC a reference to the context (dependency injection) is also stupid, because you are loading every tab in your application at application launch time, just to pass a reference. This also goes against everything Apple recommends for application architecture.
How do you resolve this? Do you use NotificationCenter to post a notification when a view controller awakes from a nib, and have the app delegate pass in the context reference then? That's about the only way I can think of that jives with both #1 and #2, but it also feels like some contortion to me.
Is there a more elegant way?
Edit: doing a notification when initializing a view controller can be a race condition, since if you're doing things with Storyboard, your tabbar's child view controllers tend to be initialized (though sans-view loading) at launch. So you'd have to do such a notification in viewDidLoad, which is a bad idea vis-a-vis MVC convention. It also ties your hands from doing anything with data models (like pre-caching for performance) before the user does anything view-related.
When you pass NSManagedObject instances to a view controller, that view controller can retain those objects. It can then get to the NSManagedObjectContext through those managed objects by calling
-[NSManagedObject managedObjectContext]
I'm not sure if this will work in your particular case, but often it will. The app delegate or the root view controller creates a context and then passes along managed objects.
Update
If you need to use the context in multiple places, there's another pattern that I've found useful:
Subclass NSManagedObjectContext:
#interface MyManagedObjectContext : NSManagedObjectContext
+ (MyManagedObjectContext *)mainThreadContext;
#end
i.e. a singleton context for the UI / main thread. This is cleaner than using the App delegate, since other classes won't have to get to the App delegate, but can use this class directly. Also the initialization of the store and model can be encapsulated into this class:
#implementation MyManagedObjectContext
+ (MyManagedObjectContext *)mainThreadContext;
{
static MyManagedObjectContext *moc;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
moc = [[self alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
// Setup persistent store coordinator here
});
return moc;
}
#end
I see that some time has passed since you posted your question, but I have a different approach that I would like to share with you and everybody else.
I assume that you want to inject the managed object context in all/some of the view controllers that are shown as tabs of the UITabViewController and that you are using a Storyboard with a UITabBarController as the rootViewController.
In your AppDelegate header make your AppDelegate to implement the UITabBarControllerDelegate protocol.
#interface AppDelegate : UIResponder <UIApplicationDelegate, UITabBarControllerDelegate>
...
In your AppDelegate implementation add the following UITabBarControllerDelegate method. It will take care of setting the managed object context in any view controller that has the property.
- (BOOL) tabBarController:(UITabBarController *)tabBarController
shouldSelectViewController:(UIViewController *)viewController {
if ([viewController respondsToSelector:#selector(setManagedObjectContext:)]) {
if ([viewController performSelector:#selector(managedObjectContext)] == nil) {
[viewController performSelector:#selector(setManagedObjectContext:) withObject:self.managedObjectContext];
}
}
return YES;
}
In your application:didFinishLaunchingWithOptions: set self as the UITabBarController delegate.
UITabBarController *tabBarController = (UITabBarController *) self.window.rootViewController;
tabBarController.delegate = self;
Sadly the first view controller to be loaded is not ready at that time (in tabBarController.selectedViewController), and doesn't invoke the delegate either. So the easiest way to set the first one is to observe the selectedViewController property of the TabBarController
[tabBarController addObserver:self forKeyPath:#"selectedViewController"
options:NSKeyValueChangeSetting context:nil];
and set it there.
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
[object removeObserver:self forKeyPath:#"selectedViewController"];
UIViewController *viewController = [change valueForKey:NSKeyValueChangeNewKey];
if ([viewController respondsToSelector:#selector(setManagedObjectContext:)]) {
if ([viewController performSelector:#selector(managedObjectContext)] == nil) {
[viewController performSelector:#selector(setManagedObjectContext:) withObject:self.managedObjectContext];
}
}
}
I would create a core data provider class, which is a singleton. This class could either just provide the persistent store so that each view controller can create it's own context if needed. Or you could use the provider class more like a manager and return new (if needed) context. It should of course also setup a merge notification to each context so that you can listen to changes between threads.
With this setup each view controller can ask the provider/manager for the context and the provider/manager will handle everything internally and return a context for the view controller.
What do you think?
IMHO I find it more elegant not to do Core Data heavy lifting in a ViewController.
I'd suggest that you encapsulate data manipulation into an object and let the object reference/encapsulate your Core Data stack.
When your application and datamodel grows, you want to be really strict with how you pass MOCs. This becomes even more important if youre using concurrency and have to deal with multiple MOCs. Besides, your ViewControllers only need a MOC for FetchedResultsControllers. In most other cases youre fine passing ManagedObjects.
Related
In my application delegate I create a NSManagedObjectContext property that I want to pass to the window controller I launch in applicationDidFinishLaunching:.
This is basically my code in AppDelegate.m:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
NSStoryboard *storyboard = [NSStoryboard storyboardWithName:#"Main" bundle:nil];
self.setupWindowController = [storyboard instantiateControllerWithIdentifier:#"setup"];
self.setupWindowController.managedObjectContext = self.managedObjectContext;
[self.setupWindowController showWindow:self];
}
In MyWindowController.m I have the following method:
- (void)setManagedObjectContext:(NSManagedObjectContext *)managedObjectContext {
((MyViewController *)self.contentViewController).managedObjectContext = managedObjectContext;
}
And this is a method from MyViewController.m:
- (void)prepareForSegue:(NSStoryboardSegue *)segue sender:(id)sender {
if ([segue.destinationController isKindOfClass:self.class]) {
((MyViewController *)segue.destinationController).managedObjectContext = self.managedObjectContext;
}
}
This approach worked pretty well until I had to programmatically launch different windows from the application delegate.
The problem now is that prepareForSegue:sender: for the initial view is called before I'm able to set the MyWindowController.managedObjectContext, so it'll only pass nil with the following segues.
How on earth should I pass objects from my app delegate to the initial window controller before it sets up the view hierarchy?
Am I approaching all this the wrong way?
I suggest reducing the dependencies among controllers by making a data model layer in your application.
The data model would manage the information that the controllers use and provide an application-specific facade in front of CoreData. The data model object(s) can be shared instances or can be requested from a known object, such as the app delegate.
That way, the model is only referenced where needed and doesn't clutter interfaces just as a pass-through object.
I used the RESideMenu story board example project download here https://github.com/romaonthego/RESideMenu however I have no idea how I inject my managedObjectContext created in AppDelegate into the main view controllers e.g. HomeViewController displayed by RESideMenu.
First of all, my advice is to avoid problems and do not use storyboards if you want to use DI.
In case you want to use them, the view controllers are created by the storyboard hence constructor injection is not possible so I assume you are using setter injection.
A simple solution is to get the app delegate's context in the in your view controller's context getter like this:
// .h
#property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
// .m
- (NSManagedObjectContext *) managedObjectContext
{
if(_managedObjectContext == nil)
{
_managedObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
}
return _managedObjectContext;
}
Yes, this is not DI since the view controllers are looking for the context instead of asking for it but is not so bad and it still allows you inject a different context with ease in your tests.
Another solution would be to override instantiateViewControllerWithIdentifier: in your UIStoryboard subclass and inject the context there, but you would need some way to check if the requested view controller needs the context or not (you could use respondsToSelector:#selector(setManagedObjectContext)), and also you would need the context in your storyboard subclass (you could either inject it or access it like in the code above). Check this question to see a similar approach but using Typhoon.
Last thing to say is that injecting a context in a view controller is prone to end up having huge view controllers, I would inject the context in other model objects and then inject those in your view controllers.
What is the proper way of setting up a separate delegate class for MapKit?
I have MapView class subclassing MKMapView and bare MapDelegate class conforming MKMapViewDelegate protocol having only one initializer method.
Here is the extract from MapView initialization method I use:
# MapView.m ...
#implementation MapView
- (id) initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// [self setShowsUserLocation:YES];
[self setDelegate:[[MapDelegate alloc] initWithMapView:self]];
The only method MapDelegate class has is
# MapDelegate.m ...
- (id)initWithMapView:(MapView *)aMapView {
self = [super init];
self.mapView = aMapView;
return self;
}
Having [self setShowsUserLocation:YES]; commented, all works fine - I see the map. If I uncomment this line, my application begins to crash.
What my MapDelegate class is missing?
UPDATE 1: if I don't use a separate class MapDelegate and set just setDelegate:self - all works.
UPDATE 2: Now I understand, that the problem with [self setDelegate:[[MapDelegate alloc] initWithMapView:self]]; string is that I need MapDelegate class to live longer than it does now (delegate property has weak attribute). If I do the following:
#property (strong) id delegateContainer;
....
[self setDelegateContainer:[[MapDelegate alloc] init]];
[self setDelegate:self.delegateContainer];
...it works! Is there a better way of retaining MapDelegate life cycle along with the one of MKMapView?
Thanks!
After waiting enough for any answers that could appear here and ensuring original problematic behavior twice more times, I am posting my own answer based on the second update from the question:
The problem with [self setDelegate:[[MapDelegate alloc] initWithMapView:self]]; string is that MapDelegate class should be able to be kept alive outside of the scope of question's initWithFrame method because delegate property has weak attribute. The possible solution is to create an instance variable serving as a container for a delegate class, for example:
#property (strong) id delegateClass;
....
[self setDelegateClass:[[MapDelegate alloc] init]];
[self setDelegate:self.delegateClass];
This solves the original problem.
LATER UPDATE
Though it is possible to set MKMapView's delegate in a separate class, I now realize that such model should not be used:
Currently I always prefer to use my controllers (i.e. controller layer in MVC in general) as delegates for all of my View layer classes (map view, scroll view, text fields): controller level is the place where all the delegates of different views can meet - all situated in controller layer, they can easily interact with each other and share their logic with the general logic of your controller.
On the other hand, if you setup your delegate in a separate class, you will need to take additional steps to connect your separate delegate with some controller, so it could interact with a rest part of your logic - this work have always led me to adding additional and messy pieces of code.
Shortly: do not use separate classes for delegates (at least view classes delegates provided by Apple), use some common places like controllers (fx for views like UIScrollView, MKMapView, UITableView or models like NSURLConnection).
I think viewDidLoad would be a better place to set up the map view. It's just a guess, but perhaps the crash is due to the view not being loaded yet.
Of course subclassing MKMapView isn't recommended at all. You would generally put your map as a subview, and set the main view to be the delegate. From the docs:
Although you should not subclass the MKMapView class itself, you can get information about the map view’s behavior by providing a delegate object.
Finally, if you really want to have a separate delegate class, you don't need to set its mapView, as all delegate methods pass the map as an argument.
I have a app with TabBar, under the main tabbar there are navigation controllers, under these there are 'branches' of tableviewcontollers.
How can I make two unrelated, non segue-connected, under two different tab icons viewcontrollers exchange information between them?
Tried with delegating but I can't get the instance variable to the delegator from the delegatee (there's no relation between them, no segue.destinationviewcontroller etc)
any ideas?
practical:
the app shows list of subitems (the parent tableview has the items), andon the other tab the recent items that were selected are getting added (but maxiumum 10 and sorted by most recent).. been breaking my ** on it...
thanks
I find NSNotifications not the best way to do this since you do couple the unrelated TableViewControllers. I think the flow of information in an application is crucial.
The solution I would personally favor is having a central class, that manages the global data for your application. This can be the AppDelegate class or an arbitrary manager class that manages the flow of data and "pulls the strings" and mediates between the different independent ViewControllers.
Example:
Using this code you can get hold of your UITabBarController and set your class as the delegate of your ViewControllers etc.:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UITabBarController *rootTabBarController = (UITabBarController *)self.window.rootViewController;
MyFirstTableViewController *firstVC = [rootTabBarController.viewControllers objectAtIndex:0];
firstVC.delegate = self; // Get informed about events in the first ViewController
MySecondTableViewController *secondVC = [rootTabBarController.viewControllers lastObject]; // Assuming you only have two Tabs
// Once you have the rootTabBarController you can cast it to the corresponding ViewController and access any nested UIViewControllers
return YES;
}
#pragma mark - MyFirstTableViewControllerDelegate
-(void)firstTableViewController:(MyFirstTableViewController *)sender didSomethingFancy:(MyFancyObject *)fancy{
// Do stuff like fetching some data, based on the event from the first ViewController
// Maybe tell the secondVC to refresh its data etc. etc.
}
Have you considered posting an NSNotification, passing the data you want to transfer in userInfo?
From the Apple documentation:
The object sending (or posting) the notification doesn’t have to know
what those observers are. Notification is thus a powerful mechanism
for attaining coordination and cohesion in a program. It reduces the
need for strong dependencies between objects in a program (such
dependencies would reduce the reusability of those objects).
The class sending a notification does it as follows:
[[NSNotificationCenter defaultCenter] postNotificationName: <notification-name>
object: self
userInfo: <your-user-info>];
and the class listening for the notification registers for notifications using:
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(someMethod:)
name: <notification-name>
object: nil];
and has a method:
- (void) someMethod: (NSNotification *) notification
{
NSDictionary * myInfo = notification.userInfo;
//do something with myInfo
}
You can use #property and #synthesize to create default getters and setters. You can aso create your own getters and setters manually.
I'm stuck with the following. In a program, I'm trying to communicate between different classes (View Controllers with NIB files attached in a TabBar application etc). I want to call a method 'OMFG' in a class called 'ProductViewDetailController'. This class is a UIViewController (SplitViewDelegate). It's loaded programmatically.
Anyways, I've been trying to get the right call to this controller, and I came up with 2 solutions. One is declaring the productviewdetailcontroller in the caller's .h file and .m file, making an IBOutlet, linking it in the Interface builder and calling it directly by the line
[productDetailController OMFG];
When I call this method, it calls the right method in the ProductViewDetailController, but the instance of this viewcontroller differs from the one I programmatically can reach with this code:
for (UIViewController *controller in self.tabBarController.viewControllers) {
NSLog(#"%#", [controller class]);
if ([controller isKindOfClass:[UISplitViewController class]]) {
UISplitViewController *cell = (UISplitViewController *)controller;
for (UIViewController *controller2 in cell.viewControllers) {
NSLog(#"%#", [controller2 class]);
if ([controller2 isKindOfClass:[ProductViewDetailController class]]) {
[controller2 OMFG];
}
}
}
Which one should I use, and why?
edit: When I try to add a SubView to both viewcontrollers, the one where the call is [controller2 OMFG]; actually shows the newly added view, where the [productDetailController OMFG]; doesn't show the newly added view... Why is that? Is there a shorter (and more chique) way to get access to the right ViewController?
You should use a IBOutlet. This makes sure your app can still call the correct target if you later decide to change the hierarchy of view controllers, for example if creating an iPhone compatible setup without a UISplitViewController.
Calling isKindOfClass: in Objective-C is a sure sign that what you are doing is probably wrong. Firstly in Cocoa Touch what you do is always more important than who you are. Secondly what you try to do is probably peeking inside something that should be left private.