1
votes

I have a bit of code in my application that generates a Core Data Model and populates it with a set of NSEntityDescriptions. Each of these entity descriptions then have an arbitrary number of NSPropertyDescriptions allocated for them. These properties are a combination of NSAttributeDescriptions and NSRelationshipDescriptions. All relationships are matched with an existing relationship and they are set as inverse of one another using setInverseRelationship:.

The attribute properties work fine, and to-many relationships work fine; I have tested that thoroughly. The issue seems to be with NSRelationshipDescriptions that have a maxCount value of 1, meaning the property description returns a isToMany value of NO. When the inverseRelationship property is set for these type of relationship, Core Data crashes when I try to save any object that utilizes that relationship with the error:

2013-11-09 11:17:15.068 Directory[1344:5c03] -[NSManagedObject count]: unrecognized selector sent to instance 0x9643820
2013-11-09 11:17:15.074 Directory[1344:5c03] *** Terminating app due to uncaught exception  'NSInvalidArgumentException', reason: '-[NSManagedObject count]: unrecognized selector sent to instance 0x9643820'
*** First throw call stack:
(
    0   CoreFoundation                      0x01f9f5e4 __exceptionPreprocess + 180
    1   libobjc.A.dylib                     0x01d228b6 objc_exception_throw + 44
    2   CoreFoundation                      0x0203c903 -[NSObject(NSObject) doesNotRecognizeSelector:] + 275
    3   CoreFoundation                      0x01f8f90b ___forwarding___ + 1019
    4   CoreFoundation                      0x01f8f4ee _CF_forwarding_prep_0 + 14
    5   CoreData                            0x0083c128 -[NSSQLCore _knownOrderKeyForObject:from:inverseToMany:] + 200
    6   CoreData                            0x00773080 -[NSSQLCore _populateRowForOp:withObject:] + 1120
    7   CoreData                            0x00789157 -[NSSQLCore recordValuesForInsertedObject:] + 71
    8   CoreData                            0x00771e8d -[NSSQLCore recordChangesInContext:] + 685
    9   CoreData                            0x00770c55 -[NSSQLCore saveChanges:] + 565
    10  CoreData                            0x0073d88c -[NSSQLCore executeRequest:withContext:error:] + 412
    11  CoreData                            0x0073d380 -[NSPersistentStoreCoordinator executeRequest:withContext:error:] + 4704
    12  CoreData                            0x00769ffc -[NSManagedObjectContext save:] + 764
    13  Directory                           0x0002f2bf -[ETDatabaseController save] + 111
    14  Directory                           0x0002ba9c -[ETDataLoader performImportWithData:] + 10284

The insinuation I am gathering from this is it is considering the inverse relationship to be a to-many relationship when that is not the case. According to my assertions, what I know about each of my relationships is:

relationDescription.inverseRelationship != nil
[relationDescription.inverseRelationship.inverseRelationship isEqual:relationDescription]

I have tested this by creating the model and populating it with a small set of sample data. Currently, objects that have any sort of attributes, to-many relationships (with/without inverse), and to-one relationship (without inverse) work consistently. The issues comes when I try to have a to-one relationship with an inverse relationship.

This seems a bit convoluted so let me know if I need to clarify anything better. Thanks!

Edit 1:

The relationship creation is done in two steps, first it creates all the relationships, then it establishes the inverse of each relationship by using a lookup.

NSRelationshipDescription *description = [[NSRelationshipDescription alloc] init];
[description setName:self.name];
[description setDestinationEntity:entity];
[description setMaxCount:(isToMany ?  0 : 1)];
[description setMinCount:0];
[description setOrdered:YES]; // See you in a minute

later...

NSEntityDescription *inverseEntity = newRelationship.destinationEntity;
NSRelationshipDescription *inverseRelationDescription = [inverseEntity relationshipsByName][inverse.name];

if (inverseRelationDescription) {
    inverseRelationDescription.inverseRelationship = newRelationship;
    newRelationship.inverseRelationship = inverseRelationDescription;
} else if ([inverse.name isEqualToString:relation.name]) {
    newRelationship.inverseRelationship = newRelationship;
}
1
Can you post the code that creates the model? Perhaps reduced to a minimal example exhibiting the problem? - Martin R

1 Answers

3
votes

Well, I guess writing Edit 1 made something click. So from what it looks like, if you call setOrdered: on a NSRelationDescription that is meant to be a to-one relationship, the internals of Core Data automatically considers it a to-many relationship, despite the conflict with the maxCount being one.

I fixed this by changing the creation code for the relationship from:

NSRelationshipDescription *description = [[NSRelationshipDescription alloc] init];
[description setName:self.name];
[description setDestinationEntity:entity];
[description setMaxCount:(isToMany ?  0 : 1)];
[description setMinCount:0];
[description setOrdered:YES];

to:

NSRelationshipDescription *description = [[NSRelationshipDescription alloc] init];
[description setName:self.name];
[description setDestinationEntity:entity];
[description setMaxCount:(isToMany ?  0 : 1)];
[description setMinCount:0];

if (isToMany)
    [description setOrdered:YES];