MagicalRecord: Create now, (possibly) save later - objective-c

I've been using MagicalRecord quite a bit lately - and blimey it's been making my Core Data life much, much easier, so full credit to the author of what is a quality library!
Anyway, the scenario is I have navigation controller stack of 3 controllers, referred here from bottom to top as A-B-C:
View controller A is a form where I would like to fill in the details about the entity (i.e., its properties).
View controller B shows the result of a calculation from the entity (e.g., via an instance function).
For the sake of simplicity, view controller C is just a confirmation & is where I'd like to save the entity.
Is there a way in MagicalRecord I can call [MyEntity createEntity] in view controller A with its properties set, pass it through to C via B & only save it in C? This would also include the possibility of not saving it at all should the user decide to go back to A from B or C.
I fully appreciate I may well be getting the wrong end of the stick with Core Data & it may not be possible. As a workaround, already I know I can create a class method that does the same calculation given the relevant parameters & pass all the parameters through the stack from A to C.
Edit: Just to make it clear, I want to call [[NSManagedObjectContext defaultContext] save] in View Controller C.

yes sure.. just dont save the managedContext.
the VCs should run all on the main thread anyways.. so all can use
[NSManagedObjectContext defaultContext]
OR
pass the MOC between the three classes using a property on the controllers.
#property NSManagedObjectContext *context;

After some testing, I knew #Daij-Djan had the right idea in that I don't call [[NSManagedObjectContext defaultContext] save] if I don't want to save my entity. As a result I leave that call until View Controller C to do the saving.
However, my testing showed I need to do a little more to avoid saving any unwanted entities. I've noticed if I go to A from B via the back button, I want to discard the entity there. In my use-case, it doesn't matter if I just create another new entity going from A to B, and I never pass back through View Controller B if I have successfully saved the entity.
So basically I need to delete the unsaved entity if the back button is pressed on View Controller B. This answer helps me massively there, resulting in this code in View Controller B:
-(void) viewWillDisappear:(BOOL)animated {
if ([self.navigationController.viewControllers indexOfObject:self] == NSNotFound) {
// self.entity is the instance of my entity
[self.entity deleteEntity];
self.entity = nil;
}
[super viewWillDisappear:animated];
}

Related

How UIViewControllers created - by alloc/init vs. performSegueWithIdentifier

My iOS 8 project uses a Storyboard, wherein I have a UINavigationController, with a root UIViewController; let's call this A. I also have another UIViewController, connected via a Segue to this root controller; let's call this B. Pretty standard configuration.
Now, A is a subclassed UITableViewController, and in the table I have a list of URLs. The app user taps a table cell, the URL is extracted and passed to B. B is a subclassed UIViewController that contains a WKWebView.
Now, because B is on the UINavigationController's stack, users can tap the Back button while in B, returning to the table with the URLs (A). The user can then tap another cell, to load another URL.
Because I need to pass some data (the URL) to B, I'm invoking it with performSegueWithIdentifier, and in the corresponding prepareForSegue, I assign the URL to a property in B.
This all works fine. My questions reflect my still-nOOb status with respect to iOS and Objective-C.
The first question, when B is created via the performSegueWithIdentifier / prepareForSegue combo, when the user taps the Back button, is this controller deallocated by the OS? In other words, there's no longer an instance of this subclassed UIViewController floating around?
If it is, then presumably it's safe to have a new instance of it created if the user taps a table cell to view another URL. In other words, repeating the call to performSegueWithIdentifier, assigning the newly chosen URL in B's property, etc. Is it safe?
Or, if it's not deallocated automatically, then does repeating the call to performSegueWithIdentifier, etc. just keep creating more and more instances of B?
And, in either case, does it make sense to, when B is first created, capture a reference to it, and keep re-using it? In other words, before calling performSegueWithIdentifier, check if a reference to B already exists, and if it does, do a pushViewController with that reference to B?
So the basic question boils down to this: In a situation where a UIViewController needs to be made visible many times - through a user action - is an instance being newly created every time it's needed? If it is, then does this also imply that every instance is also being deallocated internally when it's no longer needed? And therefore I don't need to worry about the internals?
Or, if I keep using performSegueWithIdentifier every time, am I just creating a pile of new instances of the controller, and thus being wasteful and inefficient, when I could just capture a reference to the first new instance and keep re-using it?
I've tried to step through this process in the debugger and keep track of references, but I'm unsure whether I'm seeing what I should be seeing, and so I'm asking more knowledgeable developers.
Thank you for reading through this long question, and for your time in answering it.
First of all, when you push view and pop view and if you are not retaining that view controller anywhere else then it will be released completely and it doesn't really matter how many time you call performSegueWithIdentifier to create this view if you are not retaining it or manage it reference well enough.
However, in this case, I would create your 'B' view controller once using
self.bController = [self.storyboard instantiateViewControllerWithIdentifier:#"BController"];
And then you call pushViewController using self.bController, function would be looking something like this..
- (void)presentURL:(NSString*)url
{
if ( self.bController == nil ) {
self.bController = [self.storyboard instantiateViewControllerWithIdentifier:#"BController"];
}
[self.bController setURL:url];
[viewController.navigationController pushViewController:self.bController animated:YES];
}
And refresh the WKWebView with below code when it sets the URL
-(void)setUrlString:(NSString *)urlString {
// set url string
_urlString = urlString;
// clear
[self.webView loadHTMLString:#"" baseURL:nil];
// load url with string
if( _urlString != nil ) {
// get address for hosting view
NSURL* url = [NSURL URLWithString:_urlString];
NSURLRequest* urlRequest = [NSURLRequest requestWithURL:url];
[self.webView loadRequest:urlRequest];
}
}
it will make bit simpler than prepareForSegue, however, again, repeating the call to performSegueWithIdentifier, assigning the newly chosen URL in B's property, etc. It is totally safe as long as you don't retaining anywhere else.. but performance wise it might be bit expensive than keeping one instance of B viewController and showing it multiple time because it has to read and initiate the view controller and the WKWebView instance so arguably it is bit expansive..

Core Data separate managed object context sharing same persistent store does not see changes

I am really baffled today by what I discovered.
I thought as long as all context are using the same Core Data persistent store, any changes in one context should appear in the other context after saving the context.
E.g. In view controller A I have 1 context (context A), in view controller B, I have another context (context B). Now both context A and context B point to the same persistent store.
In context A, I fetched a managed object from the persistent store, updated a property of the managed object, then I save the changes back to the persistent store with managedObjectContext save operation.
Now I open my second view controller and perform a fetch request from the same persistent store but my second view controller does not see the updated property change, until I restart the simulator.
The really strange thing is, if it's the first time I insert a new managed object into the persistent store, controller B will see the changes but subsequent changes are not shown.
I have already fixed this problem after a long battle, I just want to know why having two separate context (both on main thread of course) sharing the same persistent store does not see the changes until simulator restart.
For those who want to know how I fixed it, in my base view controller which controller A and B both inherit from, instead of alloc-initing a new context (a hence why controller A and B have two separate contexts), I told the base view controller to reference the context in the app delegate (as a result, controller A and B now point to the same context).
Here's a diagram explaining what I am seeing:
The resulting value fetched in second view controller is the old value.
If I commit some data to the persistent store, it should become permanent and accessible ANYWHERE as long as I am fetching from that same persistent store, unless I am misunderstanding that managedObjectContext save: doesn't actually make the changes to persistent store immediately until the app results.
Zhang,
without details it's difficult to figure out the problem.
A simple suggestion is to verify if you merge changes between the two different context within your app. In other words, you need to verify the main context merges changes that come from the other one. This could simple achieved like the following.
Register for this notification where, for example, in your AppDelegate or you are creating the Core Data stack.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:nil];
Implement the contextChanged: method to merge changes.
- (void)contextChanged:(NSNotification*)notification
{
if ([notification object] == [self managedObjectContext])
return;
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(contextChanged:) withObject:notification waitUntilDone:YES];
return;
}
[[self managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
}
For further info see Marcus Zarra tutorial.
In addition, if you use tables in combination with NSFetchedResultsController remember to implement delegate's methods. For info see NSFetchedResultsControllerDelegate class.
Hope that helps.

Objective-C / Cocoa Touch: Is it good design to set label texts in the setter of the UILabel object?

Let's say I have a two subclasses of UIViewController called MasterViewController and DetailViewController.
DetailViewController has a property of type NSNumber called level and a UILabel called levelLabel.
MasterViewController has a segue to DetailViewController called ToDetail. MasterViewController's prepareForSegue is like so
- (void)prepareForSegue:(UIStoryboardSegue)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"ToDetail"]) {
DetailViewController *detailVC = (DetailViewController *)segue.destinationViewController;
detailVC.level = [NSNumber numberWithInt:10]; // never mind the literal...pretend there was some algorithm for it
}
}
So then, in DetailViewController we implement the setter for levelLabel like so:
- (void)setLevelLabel:(UILabel *)levelLabel
{
if (levelLabel) {
_levelLabel = levelLabel;
_levelLabel.text = level.stringValue;
}
}
Is this good code design? Also, could you critique my code writing style? I pretty much wrote all this code on the fly so this is pretty much how I write code for the most part.
I thought of this question while showering because this is how I implement the setting of almost all the label texts that depend on a segue.
What follows is my own way of thinking about such relationships. Italics applies to your question.
You have the thing being controlled (the label) the controller (destination view controller) and the context it is being controlled within (the source view controller). This can also be expressed as model-view-controller, but I think thinking about a context can apply to much more specific and localised situations.
You should generally try to keep information flow going in one direction, from the context downwards. Objects should not have to be aware of the context in which they exist, ie they shouldn't have to ask for any information, they should be told everything they need to operate. So the source view controller should push the level to the destination view controller, the destination view controller should push this information to the label. This is what you already have, sort-of.
To build upon the above, not only should information flow in one direction, but I also try to ensure the relationships are causal, ie pushing information from one object to another should cause it to subsequently be pushed to the next object. Your code is not doing this which is probably why you have a bad feeling about it.
A more appropriate thing to do is set the text property of the label within the level setter, so that when you set or change the level, the label will update subsequently. The label may or may not be loaded so you will have to check whether it is using -isViewLoaded; -viewDidLoad is the appropriate place to set the text property upon first load.
(When I say 'push' that's just my way of thinking about setting properties or passing arguments because it implies directionality. It is really dependency injection. An example of pulling information would be delegates and data sources. But note here still the object isn't aware of any context, delegates and data sources are clearly defined as protocols, not classes, and usually within the same header file, and are themselves pushed onto the object from a surrounding context. So yes the object is asking for information, but on its own terms and from a system it has no knowledge of.)
Re coding style:
That's exactly how I write code but note Apple reserves the use of underscore prefixes

Sharing data between multiple view controllers with Core Data

I have a handful of view controllers that inherit from a subclass of UIViewController, we will call it SpecialViewController. The point of SpecialViewController is to store all of the redundant properties, etc that each of the other view controllers can inherit from. The lifecycle of the application goes from the first to the last view controller, collecting a few pieces of information, so that by the last view controller, there is a full entry of data to be saved to Core Data. Currently my AppDelegate is what holds my model, context, and persistent store coordinator, and I have a subclass of NSManagedObject called Person. My question is, what is the best way to build up the data to the last view controller, then save it?
Can I pass an instance of my Person NSManagedObject from view controller to view controller until it's ready to be committed to the Core Data database?
If so, would it be bad design to have the designated initializer of my SpecialViewController be a method like:
-(id)initWithManagedObjectInstance:(Person *)personManagedObject
and then in the end I could take the completed set of "Person" data (in the last view controller) and commit it to the database...?
Feel free to argue my idea, I want to build it the best way possible.
No, I don't think that will work. The way you would normally do this, or at least the way I do it, is to pass your ManagedObjectContext from your AppDelegate to your first SpecialViewController and from there pass it to each successive SpecialViewController. Make sure SpecialViewController has a property defined as #property (nonatomic, retain) NSManagedObjectContext *managedObjectContext; and then set it as the ManagedObjectContext of whatever class you are in: specialViewController.managedObjectContext = self.managedObjectContext; for each new SpecialViewController you create. Then you can save it at the end with:
NSError *error;
if (![self.managedObjectContext save:&error]) {
NSLog(#"Error saving: %#", error);
// Error handling
}
EDIT: Oh wait, I see what you are saying, sorry. Yes, I think you can. lol. Haven't tried it though. I would probably do this by creating a Person unaffiliated with core data, passing that from view to view, and then when i needed to save it, creating one affiliated with core data with insertNewObjectForEntityForName:inManagedObjectContext: and copying all of the info from the other one to the new one.

Cocoa Touch UIViewController Properties and class design

I'm creating a custom ViewController. This VC needs to load some data that is known in the event that creates it and pushes it to the top of the NavigationController that it is going to be part of.
My question is, how should I pass data from the view that handles the custom ViewController's creation into that custom ViewController.
I've thought of four possible options, and I was hoping to get feedback on why each one is good or not for this functionality.
1) Expose public properties in the custom ViewController and set the UI elements in the view based on those properties in - (void) ViewDidLoad.
2) Expose the actual UI elements themselves and set their .text/.image/.whatever attributes as the ViewController is being created.
3) Create a custom constructor for the custom view and pass in the values I need to set up the UI elements
4) Create a custom model that both views have access to, set the data before the CustomView is created/pushed, and access that data in the ViewDidLoad event.
I'm still new to all of this, and I want to make sure that I understand the proper handling of these handoffs of data. It seems like something like this is probably a simple answer, but I'm still a little confused and its probably really important to do this right to avoid memory loss/leaks.
Also, in case anyone cares, I'm using Stanford's CS193p class on iTunes U and Mark/Lamarche's "Beginning iPhone Development" to teach myself cocoa for the iPhone. I'm working on an application with a NavigationController and a couple ViewControllers (Presence 1 if you're familiar with 193p).
Well, I believe there are advantages & disadvantages to each of those methods depending on your requirements...often it will require some combination of approaches. I believe the most common, for me anyway, is to do something like this where you give it enough to get started.
MyViewController *vc = [[MyViewController alloc] init]; // (or initWithNibName:bundle:)
// transfer vc values here
vc.value1 = aValue;
vc.value2 = anotherValue;
[self.navigationController pushViewController:vc animated:YES];
[vc release];
After your view controller is instantiated you have an opportunity to pass objects to it. Say MyViewController is a detail view then you'd give it the object it will be displaying the details for. Or, if it's a table view you can give it the NSArray it will need for display. Then in viewDidLoad or awakeFromNib or awakeFromCoder, or... you can fill out the view...so to speak.
#1 is fine, with or without #3 (these two are not mutually exclusive)
#4 is my preferred solution. For instance, if I had a UserViewController, I would probably also like to have a User object and create it this way:
User *user = [self.users objectAtIndex:someIndex];
UserViewController *uvc = [[[UserViewController alloc] initWithUser:user] autorelease];
#2 is not a good idea. Objects should not access the UI elements of other objects. Much trouble comes from this when you decide to change your UI around (and you will).