Reputation: 3071
I am currently working on core data and newbie, fetching composite name and contact numbers from address book. I now can save data in to-one & to-many relationship using KVC and subclassing NSManagedObject. But still some concepts are not clear to me.
In normal DB we save data in primary & foreign key way. ID, compositeName fields in ContactName table and ID, number, type fields in ContactNumber table. Relationships can be easily created on both table ID fields.
In core data its done like this using KVC:
1. NSManagedObject *contactNameEntity = [NSEntityDescription insertNewObjectForEntityForName:@"ContactName" inManagedObjectContext:self.managedObjectContext];
2. [contactNameEntity setValue:@"TheName" forKey:@"compositeName"]
3. NSMutableSet *relationAsSet = [contactNameEntity mutableSetValueForKey:@"contactNameRelation"];
//fetching multi values like contact mobile, iPhone, home, work, other numbers and iterating them
4. ABMutableMultiValueRef multiValues = ABRecordCopyValue(ref, kABPersonPhoneProperty);
5. CFArrayRef mVArray = ABMultiValueCopyArrayOfAllValues(multiValues);
6. for(int i=0; i<CFArrayGetCount(mVArray); i++) {
7. NSManagedObject *contactNumberEntity = [NSEntityDescription insertNewObjectForEntityForName:@"ContactNumber" inManagedObjectContext:self.managedObjectContext];
8. [contactNumberEntity setValue:(__bridge NSString*)CFArrayGetValueAtIndex(mVArray, i) forKey:@"number"];
9. [contactNumberEntity setValue:(__bridge id)(ABMultiValueCopyLabelAtIndex(multiValues, i)) forKey:@"type"];
10. [relationAsSet addObject:contactNumberEntity];
}
11. [contactNameEntity setValue:relationAsSet forKey:@"contactNameRelation"];
Now with entity subclasses method #1:
1. ContactName *contactNameEntity = [NSEntityDescription insertNewObjectForEntityForName:@"ContactName" inManagedObjectContext:self.managedObjectContext];
2. [contactNameEntity setCompositeName:(__bridge id)(compositeName)];
3. for(int i=0; i<CFArrayGetCount(mVArray); i++) {
4. ContactNumber *contactNumberEntity = [NSEntityDescription
5. insertNewObjectForEntityForName:@"ContactNumber" inManagedObjectContext:self.managedObjectContext];
6. [contactNumberEntity setNumber:(__bridge NSString*)CFArrayGetValueAtIndex(mVArray, i)];
7. [contactNumberEntity setType:(__bridge id)(ABMultiValueCopyLabelAtIndex(multiValues, i))];
8. [contactNameEntity addContactNameRelationObject:contactNumberEntity];
}
Now with entity subclasses which is similar to KVC method #2:
1. ContactName *contactNameEntity = [NSEntityDescription insertNewObjectForEntityForName:@"ContactName" inManagedObjectContext:self.managedObjectContext];
2. [contactNameEntity setCompositeName:(__bridge id)(compositeName)];
3. NSSet *set = [contactNameEntity contactNameRelation];
4. NSMutableSet *mSet = [NSMutableSet setWithSet:set];
5. set = nil;
6. for(int i=0; i<CFArrayGetCount(mVArray); i++) {
7. ContactNumber *contactNumberEntity = [NSEntityDescription
8. insertNewObjectForEntityForName:@"ContactNumber" inManagedObjectContext:self.managedObjectContext];
9. [contactNumberEntity setNumber:(__bridge NSString*)CFArrayGetValueAtIndex(mVArray, i)];
10. [contactNumberEntity setType:(__bridge id)(ABMultiValueCopyLabelAtIndex(multiValues, i))];
11. [mSet addObject:contactNumberEntity];
}
12. [contactNameEntity addContactNameRelation:mSet];
Questions:
In KVC method line 3, why a normal NSSet cannot be created?
Why relationship is present as NSSet, although in normal DB relationship is just a simple thing but seems like complex in core data.
Am I using the subclass generated methods correctly or Am I using subclass generated methods in standard way? (asking to verify my concepts).
Why method #1 and method #2 differs? both are using subclasses.
Is @property (nonatomic, retain) NSSet *contactNameRelation;
and NSMutableSet *relationAsSet = [contactNameEntity mutableSetValueForKey:@"contactNameRelation"];
return same object/value?
*. Please avoid any silly mistake.
*. Please tell the questions I am asking is unclear.
Edit 1
ContactName entity xcode generated subclass .h file:
@property (nonatomic, retain) NSString * number;
@property (nonatomic, retain) NSSet *contactNumbers;
@end
@interface ContactName (CoreDataGeneratedAccessors)
- (void)addContactNumbersObject:(ContactNumber *)value;
- (void)removeContactNumbersObject:(ContactNumber *)value;
- (void)addContactNumbers:(NSSet *)values;
- (void)removeContactNumbers:(NSSet *)values;
and this is .m file:
@implementation ContactName
@dynamic compositeName;
@dynamic contactNumbers;
@end
I want to know how to correctly use these methods, and thats why method 1 and 2 are two different approaches I posted above based on this subclass. Whats correct and whats not please help me out.
Edit 2
this is what I get when I log relationAsSet
$0 = 0x0854c1c0 Relationship 'contactNameRelation' on managed object (0x835bae0) <ContactName: 0x835bae0> (entity: ContactName; id: 0x835ba80 <x-coredata:///ContactName/t66752D00-F08B-40DF-AEED-ABCACEA254652> ; data: {
compositeName = myname;
contactNameRelation = (
);
}) with objects {(
)}
and this
$0 = 0x0821b460 Relationship 'contactNameRelation' on managed object (0x823fe50) <ContactName: 0x823fe50> (entity: ContactName; id: 0x823fea0 <x-coredata:///ContactName/t3CED08C3-94E0-4B43-93F0-96DE2D2A24922> ; data: {
compositeName = myname;
contactNameRelation = (
"0x821be40 <x-coredata:///ContactNumber/t3CED08C3-94E0-4B43-93F0-96DE2D2A24923>"
);
}) with objects {(
<ContactNumber: 0x8241ab0> (entity: ContactNumber; id: 0x821be40 <x-coredata:///ContactNumber/t3CED08C3-94E0-4B43-93F0-96DE2D2A24923> ; data: {
contactNumberRelation = "0x823fea0 <x-coredata:///ContactName/t3CED08C3-94E0-4B43-93F0-96DE2D2A24922>";
number = 123;
type = iphone;
})
)}
Upvotes: 0
Views: 374
Reputation: 2779
You will receive a NSMutableSet *
, but you can store it in NSSet *
since NSMutableSet *
is a subclass of NSSet *
. The issue is that you should do this ONLY, if you are not going to add or remove elements from the set.
In fact you can do this a lot more easier. You can do this:
contactNumberEntity.contactNumberRelation = contactNameEntity;
In general yes, but you are writing a lot more code than is necessary taking all these middle steps as retrieving a set, etc... It is entirely unnecessary.
I do not understand where you are heading with this one. In #2 you take the middle step and store the relationship in a NSSet *
type pointer, then create a new set, NSMutableSet *
with objects from the first set copied into it. #1 is the way to go from those two alternatives.
I stand corrected! To be sure about this, I made an experiment and found out that not only should you treat them as two different objects, the sets returned by two distinct approaches do actually have different memory addresses, therefore they are different objects.
Update #1
The reason for doing
NSMutableSet *relationAsSet = [contactNameEntity mutableSetValueForKey:@"contactNameRelation"] ;
is to acquire the relationships that are already in place. If you are not interested in existing relationships, you can skip this step entirely. Of course, if you have made the effort to subclass your NSManagedObject
, you will never want to do it like this.
Xcode generates them as NSSet *
, because Apple prefers you alter the set through special methods. Core Data is responsible for maintaining your inverse relationships. To do that they require you to honor the public interface and use those methods.
With KVC approach there is a slight difference. There they will return a proxy object that is Key-Value-Observable. That is why you may mutate the set you retrieve through KVC and that is also why it is returned as NSMutableSet *
, to let you know that this is okay.
If you want to add or remove a single object in that relationship, you can use:
- (void)addContactNumbersObject:(ContactNumber *)value;
- (void)removeContactNumbersObject:(ContactNumber *)value;
If you prefer to replace the entire set of relationships, you can use:
- (void)addContactNumbers:(NSSet *)values;
- (void)removeContactNumbers:(NSSet *)values;
Here is a link to Core Data Programming Guide: Using Managed Objects.
The thing that @MartinR was pointing out is that when you want to alter your managed object, you need to work with the direct set that either Core Data methods provide or through KVC. When you do this,
NSMutableSet *mSet = [NSMutableSet setWithSet:set];
you are actually creating a brand new set that will have all the objects from the old set copied to. And I was incorrect to call this casting - it is not.
And now a very important piece of the puzzle. I quote the Apple documentation (also referenced above) here:
It is important to understand the difference between the values returned by the dot accessor and by mutableSetValueForKey:. mutableSetValueForKey: returns a mutable proxy object. If you mutate its contents, it will emit the appropriate key-value observing (KVO) change notifications for the relationship. The dot accessor simply returns a set. If you manipulate the set as shown in this code fragment:
[aDepartment.employees addObject:newEmployee]; // do not do this!
then KVO change notifications are not emitted and the inverse relationship is not updated correctly.
End of Quote.
Never ever cast your NSSet *
retrieved in your NSManagedObject subclass methods into NSMutableSet *
.
Update #2
Expanded my explanation about why Xcode generates accessors that return NSSet *
and why you may not cast it to NSMutableSet *
to modify relationships.
Upvotes: 1
Reputation: 539945
As @svena already said, your names for the relationships are a bit "unconventional" (and it seems that you partially changed them already in your added code). So I will assume the following model and relationship names:
The value of a to-many relationship is a NSSet
. For example
ContactName *aContact = ...;
NSSet *relatedNumbers = aContact.contactNumbers;
// or equivalently, using KVC:
relatedNumbers = [aContact valueForKey:@"contactNumbers"];
for (ContactNumber *contactNumber in relatedNumbers) {
NSLog(@"%@", contactNumber.number);
}
So you use the value of the relationship (a NSSet
in this case) to traverse the object
graph and find related object. But you cannot modify this set (it is immutable).
Then you have questions about
NSMutableSet *mutableRelatedNumbers = [aContact mutableSetValueForKey:@"contactNumbers"];
which is a very special thing. It is a mutable set that is "tied" to the aContact
object. That means if you modify this set (add or remove something) then you effectively modify
the contactNumbers
relationship of the aContact
object.
So
ContactName *aContact = ...;
ContactNumber *aContactNumber1 = ...;
ContactNumber *aContactNumber2 = ...;
// (A)
NSMutableSet *mutableRelatedNumbers = [aContact mutableSetValueForKey:@"contactNumbers"];
[mutableRelatedNumbers addObject:aContactNumber1];
[mutableRelatedNumbers addObject:aContactNumber2];
(which is the corrected version of your first code block) is a complicated way of doing this
// (B)
[aContact addContactNumbersObject:aContactNumber1];
[aContact addContactNumbersObject:aContactNumber2];
(which is your method #1). Another equivalent method is (because of the inverse relationship)
// (C)
aContactNumber1.contactName = aContact;
aContactNumber2.contactName = aContact;
or
// (D)
NSMutableSet *mset = [NSMutableSet set];
[mset addObject:aContactNumber1];
[mset addObject:aContactNumber2];
[aContact addContactNumbers:mset];
which is similar to (B), and is the corrected version of your method #2.
This are all valid methods to add an object to a relationship, but (C) is the easiest to use, followed by (B). (A) and (D) are rarely used. Another advantage of (C) is the better type checking.
So even if I did not answer the questions directly, I hope that this helps to clear things up!
Upvotes: 1