What is the correct way to trigger an NSNotification
whenever an item is added to or removed from a to-many relationship / NSSet
within a custom NSManagedObject
subclass?
I have a custom NSManagedObject
subclass that has a one-to-many unordered relationship to another NSManagedObject
subclass. For clarity, let's say these two subclasses are Teacher
and Student
, where one Teacher
can have multiple Student
objects but where each Student
is only assigned to one Teacher
.
I'd like to be able to trigger a notification whenever a Student
is added to or removed from a Teacher
, whether because Student
was simply assigned to or from a Teacher
or whether because Student
was deleted entirely from Core Data.
I tried using KVO but it doesn't seem like you can add an observer to an add an observer to a NSSet
's count
property@dynamic
property. Additionally, I tried implementing my own custom to-many accessor method as outlined in Apple's documentation, but in testing it seems like my custom accessor methods are never called. In case there is something wrong with my implementation, here is how I implemented it within Teacher
:
@implementation Teacher
@dynamic students;
- (void)addStudentsObject:(Student *)value
{
NSSet *changedObjects = [[NSSet alloc] initWithObjects:&value count:1];
[self willChangeValueForKey:NSStringFromSelector(@selector(students))
withSetMutation:NSKeyValueUnionSetMutation
usingObjects:changedObjects];
[[self primitiveStudents] addObject:value];
[self didChangeValueForKey:NSStringFromSelector(@selector(students))
withSetMutation:NSKeyValueUnionSetMutation
usingObjects:changedObjects];
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_TEACHER_STUDENT_WAS_ADDED object:self];
}
- (void)removeStudentsObject:(Student *)value
{
NSSet *changedObjects = [[NSSet alloc] initWithObjects:&value count:1];
[self willChangeValueForKey:NSStringFromSelector(@selector(students))
withSetMutation:NSKeyValueMinusSetMutation
usingObjects:changedObjects];
[[self primitiveStudents] removeObject:value];
[self didChangeValueForKey:NSStringFromSelector(@selector(students))
withSetMutation:NSKeyValueMinusSetMutation
usingObjects:changedObjects];
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_TEACHER_STUDENT_WAS_REMOVED object:self];
}
- (void)addStudents:(NSSet *)values
{
[self willChangeValueForKey:NSStringFromSelector(@selector(students))
withSetMutation:NSKeyValueUnionSetMutation
usingObjects:values];
[[self primitiveStudents] unionSet:values];
[self didChangeValueForKey:NSStringFromSelector(@selector(students))
withSetMutation:NSKeyValueUnionSetMutation
usingObjects:values];
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_TEACHER_STUDENTS_WERE_ADDED object:self];
}
- (void)removeStudents:(NSSet *)values
{
[self willChangeValueForKey:NSStringFromSelector(@selector(students))
withSetMutation:NSKeyValueMinusSetMutation
usingObjects:values];
[[self primitiveStudents] minusSet:values];
[self didChangeValueForKey:NSStringFromSelector(@selector(students))
withSetMutation:NSKeyValueMinusSetMutation
usingObjects:values];
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_TEACHER_STUDENTS_WERE_REMOVED object:self];
}
...
@end
The fundamental issue turns out to be that the fancy accessor examples in the Apple Docs that you were using as a reference are actually not all the accessors for the relationship. They are extra convenience accessors that you can implement if you want. But because you are trying to insert your code by overriding all the setters (or whatever you want to call mutating accessors), you need to also override the most basic setter.
The most basic accessors for a to-many relationship are setters and getters for the NSSet
object for the entire relationship. In your case, - (NSSet*) students
and - (void) setStudents:(NSSet*)students
.
So, at a minimum, your notification needs to be posted in setStudents
:
-(void) setStudents:(NSSet*)students{
[self willChangeValueForKey:@"students"];
[self setPrimitiveValue:students forKey:@"students"];
[self didChangeValueForKey:@"students"];
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_TEACHER_STUDENTS_WERE_ADDED object:self];
}
That is the setter being used by default.