Cannot perform operation because childrenKeyPath is nil - objective-c

Why do I get this crash error when I try to insert a child into my NSTreeController?
NSTreeController *tree = [NSTreeController new];
NSTreeNode *node1 = [NSTreeNode treeNodeWithRepresentedObject:#{ #"type": #"shirt" }];
// The below method causes mysterious crash
[tree insertObject:node1 atArrangedObjectIndexPath:[NSIndexPath indexPathWithIndex:0]];
Xcode says:
* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '[object class: NSMutableDictionary] Cannot perform operation because childrenKeyPath is nil'
*
Why is causing this error? What is childrenKeyPath and why do I need it (I'm not using interface builder)?

I've made several mistakes here. You should not insert instances of type NSTreeNode into an NSTreeController with insertObject. You use insertObject to insert a model object, at which point an NSTreeNode will be automatically created for it.
Also, in your model object, you need to have a property or key which is set as an NSMutableArray. Then, before you insert any model objects, you set the childrenKeyPath property of NSTreeController to equal the name of that property or key.
This is because NSTreeController and NSTreeNode are not designed to hold data or child objects themselves, but act as a simple but helpful "map" for the models you've created and retained.
I hope this information helps others because the rest of StackOverflow was oddly silent about it...

Related

Add NSArray to CLBeaconRegion via setValue forKey causes Error

Is it possible to add an Array to a CLBeaconRegion via a setValue forKey?
I've tried to add it:
[regionAdvert setValue:haveArray forKey:#"advertArray"];
But I receive just the following error:
> Terminating app due to uncaught exception 'NSUnknownKeyException',
> reason: '[<CLBeaconRegion 0x15379600> setValue:forUndefinedKey:]: this
> class is not key value coding-compliant for the key advertArray.'
setValue:forKey: is a KVC method which is basically available on every class - but that doesn't mean you can just pass anything to it. The key you use needs to be an existing property (well, really a method as that is what gets called, or a handled non-existent key). Anyway, the point is that you should know the key exists before trying to set it.
Technically you can use objc_setAssociatedObject to associated arbitrary objects with other objects but in this case I would encourage you to instead use the major and minor properties to check the purpose of the region before deciding how to display your alert.
No, you can't do this. If you want to associate other data with a CLBeaconRegion, then you can use other data structures like NSDictionary. Since each CLBeaconRegion is constructed with a unique "identifier" string, you can use this identifier as a key into a NSDictionary to store your other arbitrary data objects.
Also, you aren't supposed to change a CLBeaconRegion once constructed. The fields are immutable. This isn't really a big deal -- there are only three fields. Just create a new CLBeaconRegion, copying any of the identifiers from the old CLBeaconRegion that you wish to retain.

Core Data To Many Peer Relationship

I'm writing a Core Data based Cocoa app for recipes. I have an Ingredient entity, and want to create a ingredientSubstitutes To Many relationship to other Ingredients, but I'm getting errors either when setting the relationship or when saving the store that I can't figure out. Here's the Entity description:
Ingredient
Attributes:
ingredientName type:String
Relationships:
ingredientSubstitutes destination:Ingredient inverse:ingredientSubstitutes
In my Nib I have 3 array controllers:
All ingredients AC
Available substitutes AC
Selected ingredient substitutes AC
I have 3 table views that each display the contents of these array controllers. I then have a button to add one ingredient as a subsitute for another, that is bound as follows
Button bindings
Target: All Ingredients AC.selection
Selector Name: addIngredientSubstitutesObject:
Argument: Available Substitutes AC.seletion
With this setup, as soon as I click the add button, the app throws unrecognized selector sent to instance exception: "-[_NSControllerObjectProxy entity]: unrecognized selector sent to instance", as if Ingredient doesn't recognize addIngredientSubstitutesObject. I added a proxy method to make sure that's the selector that's not recognized, and that is indeed the problem.
After trying a bunch of things and getting no where, as an experiment, I then changed the model, so that ingredientSubstitutes has no inverse:
Ingredient
Attributes:
ingredientName type:String
Relationships:
ingredientSubstitutes destination:Ingredient inverse:*none*
When I run this the add is successful, and all the tables update accordingly, but on save, I get a different unrecognized selector and the app throws an exception:
-[_NSControllerObjectProxy _isKindOfEntity:]: unrecognized selector sent to instance
Any suggestions as to what might be going on? Am I taking the wrong approach?
Figured it out, this thread helped tip me off: Core Data Programmatically Adding a to-many Relationship
Basically, binding the target to the Array Controller's selection meant that the target object was an Array Controller's Proxy object for an Ingredient, not an actual Ingredient, which apparently does not respond to the To Many accessors that Ingredient does. I solved this by instead implementing a method in the app delegate that gets the actual objects and can use the To Many accessors:
- (void)addSubstituteForSelectedIngredient:(OFIngredient *)ingredient
{
OFIngredient *selectedIngredient = [[self.allIngredientsArrayController selectedObjects] objectAtIndex:0];
OFIngredient *selectedSubstitute = [[self.availableSubsArrayController selectedObjects] objectAtIndex:0];
[selectedIngredient addIngredientSubsObject:selectedSubstitute];
}
Note that NSArrayController's - (id) selection method (which I tried before, as mentioned in the original description) returns a proxy object, so you must use (NSArray *)selectedObjects!

CoreData - trouble accessing relationships

My data model looks like this:
Object A <----->> Object B <-----> Object C
I fetch a group of Object A's from Core Data via an NSFetchedResultsController. For one particular object in this group, I know that it has only one Object B related to it and I want to retrieve the Object C.
I'm trying to do that like this:
NSArray *bArray = [objectA.relationA allObjects];
ObjectB *myB = bArray[0];
ObjectC *myC = myB.relationB;
(I've also tried [myB valueForKey:#"relationB"] with the same result)
The problem is that I can't get the fault to fire for Object C - I keep getting this for myC:
$6 = 0x0a947c00 (entity: ObjectC; id: 0xa9680b0 ; data: )
I'm passing this value on to another view controller and it's still a fault when it's accessed there, which isn't terribly useful.
It seems silly to have to refetch when I have the object, but I don't know what else to do. All the threads I can find on this say that faults are normal and that they will be fired when you access the faulted object, but that doesn't seem to be happening here.
Any suggestions?
Update: I tried adding this:
[fetchRequest setRelationshipKeyPathsForPrefetching:#[#"relationA.relationB"]];
But it did not make any difference.
The problem is that I can't get the fault to fire for Object C - I
keep getting this for myC:
$6 = 0x0a947c00 (entity: ObjectC; id: 0xa9680b0 ; data: )
You haven't tried to access myC yet. myC will remain a fault until you use it somehow. From the docs:
Fault handling is transparent—you do not have to execute a fetch to
realize a fault. If at some stage a persistent property of a fault
object is accessed, then Core Data automatically retrieves the data
for the object and initializes the object (see NSManagedObject Class
Reference for a list of methods that do not cause faults to fire).
This process is commonly referred to as firing the fault.
So, (assuming ObjectC has a name property) if you do something like:
NSString *name = myC.name;
you should find that the fault at myC fires and you'll automagically have a real object to work with.
All the threads I can find on this say that faults are normal and that
they will be fired when you access the faulted object, but that
doesn't seem to be happening here.
They're right. Unless there's more that you haven't told us, it sounds like you're just expecting the fault to fire at a different time, i.e. when you assign the object to myC. But again, the fault won't fire until you do something to the fault, like getting or setting a property.

NSTreeNode mutableChildNodes not working as it should?

I'm totally baffled why this is not working. I'm trying to insert a new NSTreeNode into a mutable array of child nodes. Here's the code:
NSTreeNode *newNode = [[NSTreeNode alloc] init];
NSMutableArray *children = [anExistingParentTreeNode mutableChildNodes];
[children addObject:newNode];
Upon execution I get all sorts of errors:
-[NSCFSet initWithObjects:count:]: attempt to insert nil object at objects[0]
-[NSTreeNode _tearDownObserving]: unrecognized selector sent to instance 0x2000bff40
Serious application error. Exception was caught during Core Data change processing: -[NSTreeNode _tearDownObserving]: unrecognized selector sent to instance 0x2000bff40 with userInfo (null)
The errors seem to be dealing with KVO stuff. Has anybody encountered errors like these using mutableChildNodes? Any help is greatly appreciated.
Note: The underlying NSTreeController IS bound to core data via managed object context.
Could it be that you have not initialized the newNodeobject correctly?
The only init method defined for the class is:
- (id)initWithRepresentedObject:(id)modelObject
When you use init, you just use the default implementation inherited from NSObject.
Normally, a class has one or more designated initializers, but in the case of NSTreeNode, I can't see that it is specified in the docs. However, since there is only one initializer defined for the class, and no setter methods to set the represented object at a later stage, I'd conclude that initWithRepresentedObject: is the designated initializer of the class.
About initializers: http://developer.apple.com/library/ios/#documentation/general/conceptual/DevPedia-CocoaCore/MultipleInitializers.html
Refer to my last comment on the original question.

Problem assigning value obtained from [array objectAtIndex:]

In my Piano class, I have a property (Keys is another custom class)
#property (nonatomic, retain) Keys *lastPlayed;
In one of my Piano methods, I set the value of lastPlayed using an object from an array of Key objects.
self.lastPlayed = [allKeys objectAtIndex:variable];
The above line of code causes the program to crash.
I've noticed that if I hardcode a specific Key object from the allKeys array, then it works fine. Like so:
self.lastPlayed = keyC;
Interestingly, it doesn't crash if I put the crashing code into a different method.
How can I prevent the crash?
EDIT:
I call this method in the Keys class, where my piano is the delegate
[delegate deliverTagwithNameTag:self.tag]
the piano then responds
- (void) deliverTagwithNameTag:(int)nameTag {
self.lastPlayed = [allKeys objectAtIndex:nameTag];
}
You're probably not getting a plain unexplained crash, you're probably raising an exception. You can watch the console to find out which exception you raised. Normally in this sort of situation it'll be something useful to tell you either that you're asking the array for an out-of-bounds value (which could happen if variable were collecting an incorrect value somehow), that the array itself is invalid (which could be a memory allocation problem) or that the thing returned can't be stored as lastPlayed (which would normally indicate you're doing something custom in the setter and getting an unexpected type of class).
So to prevent the crash, check your console and look for one of those problems.