Why is awakeFromInsert called twice? - objective-c

I have implemented awakeFromInsert to set some default values and relationships in my core data objects. However, the method is being called twice, meaning that the to-many values I am adding are being added multiple times.
I am using parent and child managed object contexts.
What gives?

awakeFromInsert will be called when you insert the object into its initial context. When this context is saved and the changes are pushed up to the parent context, it will be called again.
You can query the self.managedObjectContext property to determine which case the method is being called for. Depending on your particular use case, you may want to check for the presence or absence of a parentContext and act accordingly.

thanks to jrturton help:
here is the simplest one:
when parentContext is null, means when this context is saved you can do you custom logic, for example incrementing table number
- (void)awakeFromInsert
{
if (!self.managedObjectContext.parentContext) {
//setting tableNumber
[self willChangeValueForKey:#"number"];
[self setPrimitiveNumber:tableNumber];
[self didChangeValueForKey:#"number"];
}
}

Related

Core Data, populating mandatory relationship from a newly created NSManagedObject

This should be easy, how difficult can it be.
I have a document based core data application with a very simple data model.
I have a "node" entity with a parent/children relationship to itself controlled by a NSTreeController, and viewed through an NSOutlineView. The "node" also has a non optional (to one) relationship to another entity type "nodeProperties" which is managed by a NSArrayController. I have NSManagedObject sublasses for both of the entities. My document class has outlets bound to both the tree controller and array controller instances.
My problem is how to ensure that, when a new "node" is created by a user interface action in the outline view, its relation to a suitable (pre-existing) nodeProperties object is populated.
Approaches I have tried / considered:
Let the tree controller create the "node" (from its add:, addChild: actions) and populate the relationship to a nodeProperties object in the "node" subclass awakeFromInsert method. The trouble is I cannot find a means of accessing any nodeProperties object from within the "node"s awakeFromInsert. The "appropriate" nodeProperties object is available from a method in the document class, but accessing the document object from the node awakeFromInsert method seems to break the principles of MVC, and I have read that the shared document object is not always safe in a drag and drop operation (which in my case also creates a new node object)
Write add: and addChild: action methods in the document class and invoke these from the end user actions instead of the tree controller (My drag and drop support is also in the document class). Then from within these methods invoke the add: and addChild: methods in the tree controller, then set the nodeProperties relationship on the newly created node. The trouble is I don't know how to ask the tree controller to give me a reference to the newly created node? I have tried using the selectedObjects method to get the parent, and then comparing the parents children before and after the add to get the new node. But the children content does not change at this time - perhaps it is a delayed update?
As a variant of 2, don't use the tree controller add:/addChild: methods at all, but instead create the node entity object in the document add:/addChild: methods using the tree controllers selectedOjects to get the parent. I don't really like this since it seems like doing something behind the tree controllers back, and I would have to setContent: each time I created root objects.
I have considered the possibility of observing the creation of the newly created node, but I don't know what to observe to achieve that.
Someone must have done something like this before - but I trawled to no avail. All help, advice, guidance would be very welcome.
OK so after much trawling and experimentation the answer was a variant of 3. The document creates the new node, populating its mandatory relationship, in add and addChild action methods, and then inserts the node into the tree controller using the method
NSTreeController insertObject:atArrangedObjectIndexPath:
For those interested, this is my addChild method in the document class. It has a few specifics from my data model
- (IBAction)addChildAction:(id)sender
{
NSArray *indexPaths = [nodeTreeController selectionIndexPaths];
NSArray *selectedObjects = [nodeTreeController selectedObjects];
for (NSUInteger i = 0; i < [indexPaths count]; i++)
{
QVXpandNode *parentNode = [selectedObjects objectAtIndex:i];
if ((parentNode) && ([parentNode.isMaster boolValue])) // can only add nodes under the master node
{
QVXpandNode *createdNode = [self createPopulatedNode];
// Dont belelieve below is safe when >1 selected,
// since adding a new node will result in the tree paths changing?
// Hmmm but I do want to support multiple selection addition??
[nodeTreeController
insertObject:createdNode
atArrangedObjectIndexPath:[[indexPaths
objectAtIndex:i] indexPathByAddingIndex:[parentNode.children count]]];
}
}
}
You will see that I am unsure whether I will put the second and later children at the right path if >1 rows were selected before calling the action.
The addSibling method is slightly more complicated by the need to calculate the last index path value, but is otherwise similar. I can reproduce it if anyone wants to see it, but the key to populating a mandatory relationship in a new tree node is to do it in the document class and then tell the tree controller precisely where in the tree you want to insert it.

Cascading a property update to a related managed object

I have a managed object that has a one-to-one relationship to itself. An example of this could be a person object could be linked to one other person to represent a marriage.
If one of the properties of the person was address and it was changed for one person, how would I follow the relationship and make sure it was changed for the linked person object.
Initially I thought about doing this in the setter, i.e setAddress but I quickly realised that this would cause infinite recursion between the two objects.
What's an elegant solution to this type of problem?
You could still do that in the setter, but just check, if the destination value is already the one you want to set, return from setter, without any infinite loop.
You could try something like
- (void)setAddress:(NSString *)address
{
// common setter here
// then set your relationship's address
if (![self.personRelationship.address isEqual:address]) {
self.personRelationship.address = address;
}
}
Based on your description I would move the address into a separate entity Address that has a relationship to Person. Set up the relationship so that each person has one address and each address can be used by more than one person.
By doing this, you only need to change the address in one place, and then it immediately takes effect for any person who uses that address. If two people who have the same address later need to have different addresses, create a new Address entity for them.
1. Step
If you want to override the setter in CoreData you should follow the documentation here:
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/Articles/cdAccessorMethods.html#//apple_ref/doc/uid/TP40002154-SW14
The key in this is to use the "primitive" accessors like in the example:
- (void)setAddress:(NSString *)newAddress
{
[self willChangeValueForKey:#"address"];
[self setPrimitiveAddress:newAddress];
[self didChangeValueForKey:#"address"];
}
Then you will not get an infinite loop trying to set the attribute.
2. Step
Apply it to the address question you can set the partner's address as well.
The code will change to
- (void)setAddress:(NSString *)newAddress
{
[self willChangeValueForKey:#"address"];
[self setPrimitiveAddress:newAddress];
[self didChangeValueForKey:#"address"];
[self.partner willChangeValueForKey:#"address"];
[self.partner setPrimitiveAddress:newAddress];
[self.partner didChangeValueForKey:#"address"];
}
Alternative suggested by noa:
- (void)setAddress:(NSString *)newAddress
{
[self willChangeValueForKey:#"address"];
[self setPrimitiveAddress:newAddress];
if (![self.partner.address isEqual:newAddress])
[self.partner setAddress:newAddress];
[self didChangeValueForKey:#"address"];
}

Dependent attribute from multiples entities

One of my NSManagedObject's attribute depend on various attributes, some of them in a related NSManagedObject.
In a first run, I implemented a simple Transient Attribute for such attribute, but I just discover that it's not possible to use fetch predicate with transient properties.
I need to create an attribute so that:
Its value it's calculated using different attributes
1 of the dependent attribute is present in a related NSManagedObject
I can do fetch using this attribute as predicate.
If I update one of the dependent attributes, the calculated value must be updated
You can use Key value observing to monitor a property being changed in order to keep some calculated property up to date. You can add an observer to the property you want to monitor.
KVO Programming Guide.
edit: Reference here
To do this correctly, you'll want to override the property getter for your calculated property, and create a keyPathsFOrValuesAffecting<Key> function.
Apple's example is pretty good, it gives a case where a fullName property should be gathered from the firstName and lastName.
So you would need to implement KVO with this function:
+ (NSSet *)keyPathsForValuesAffectingFullName
{
return [NSSet setWithObjects:#"lastName", #"firstName", nil];
}
This will allow your app to be notified anytime those values are modified. Then you'd just override the getter that would be called upon this notification
- (NSString *)fullName
{
return [NSString stringWithFormat:#"%# %#",firstName, lastName];
}

How do I access the relationship name of a Core Data parent object?

I have a simple Core Data iPhone app where Parents have a one-to-many relationship with Children. In the first view, you are presented with a list of Parents; tapping on one provides the corresponding list of Children. In the Child view, I want the each cell to show the Child name, and then the Parent name as a subtitle. I have used the following command:
cell.detailTextLabel.text = [[managedObject valueForKey:#"Parent"] description];
But instead of getting the parent name, the subtitle displays something like:
<Parent: 0x4d5a520> (entity: Parent; id...
Obviously I'm printing out the actual relationship, rather than the object's name. How can I show the actual Parent name ("Mr Smith")?
Thanks.
I managed to work it out! I'm gradually getting the hang of this programming thing... :-)
I'll leave the answer here in case someone else searches for it one day...
Instead of using a generic NSManagedObjectContext with KVC, I used my own subclass:
Child *child = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = [child.name description];
cell.detailTextLabel.text = [child.parent.name description];
Your basic problem is that description is a method of NSObject that returns a description useful for programmers. If you have an attribute called description then it will collide with the built-in description method. You may have been confused because when you call description on a NSString, you get the string but if you call any other class, you don't get the literal data e.g. When you call description on a managed object you get the object UUID, whether it is a fault or not, it's attributes and the objects it's related to. All that is useless for anything but debugging.
Never use description as an attribute name and never use the return of description for anything the user ever sees.
If you have a tableview of Child objects as described above then your fetched results controller will return a managed object configured to the Child entity. To access the name of the related Parent object you would use:
cell.detailTextLabel.text = [[childMo valueForKey:#"parent"] valueForKey:#"name"];
Of course, if either the relationship or the parent.name attribute is optional, you should first check that either has a value before attempting to use the value.

How can I set a default value in NSManagedObject derived class based on existing NSManagedObject values?

I have an NSManagedObject derived class OrderMO that has a property OrderDate of type NSDate. I would like to have the default value for OrderDate be the first Monday of the month for the first month that does not already have an order associated with it.
To do this I am overriding the awakeFromInsert function of NSManagedObject and setting the value of OrderDate like this:
- (void) awakeFromInsert
{
[super awakeFromInsert];
[self setValue:[self getNextOrderDate] forKey:#"OrderDate"];
}
The custom function getNextOrderDate calculates the appropriate date and returns it. The problem is that getNextOrderDate calls executeFetchRequest which is apparently a no-no during the awakeFromInsert call. I've tried doing what is described in this article, but it did not solve my problem. In that article the problem is solved by calling self performSelector to delay the call to executeFetchRequest until after the NSManagedObject is initialized. In my case I just get 2 garbage rows.
I considered sub-classing my Orders ArrayController to override the add function, but don't really know what to do there either.
Can someone please tell me the proper way to initialize an NSManagedObject property based on existing NSManagedObjects of the same entity type, in the same array with a value that depends on the existing objects' values?
Well, one method would be to do a fetch for specific value. That returns just a dictionary and I'm pretty sure it won't trigger all the notifications associated with fetching faults or objects. You should be able to extract just the one date you want without setting off all the bells and whistles. However, I've never tested that in this particular circumstance.
Another approach would be to create the managedObject directly i.e. just like a non-managedObject, populate the attribute as you want and only then insert the managedObject into the context. Not very elegant but it will work and it won't set off a bunch of notifications.
Some Core Data hands write their own insertion class methods for their NSManagedObject subclass. You might want to employ that here. E.g.
+(OrderMO *) insertOrderMOIntoManagedObjectContext:(NSManagedObjectContext *) aContext{
OrderMO *newOrder=[[OrderMO alloc] init];
// pefrom fetch on aContext to find the next order date
newOrder.orderDate=//... fetched order date
[aContext insertObject:newOrder];
}