views:

81

answers:

2

Hello all,

In page 40 of Marcus Zarra's Core Data book, he suggests that, since NSTreeController requires the same key for accessing all the objects in the hierarchy (for example, children) and that could imply less meaningful relationship names, you can write additional accessors for the desired relationships. I think that this is a great idea, but I am not sure of how to implement it.

Let me use Aperture’s data model as an example: You can have many libraries, each one of them can have many projects, and each of them can have many photos. So, if I name my Entities Library, Project, and Photo and their relationships projects, photos and nothing respectively, is the following a proper implementation for Library?

Library.h

@interface Library: NSManagedObject {
}

@property (nonatomic, retain) NSString *title;
@property (nonatomic, retain) NSSet *projects;
@property (nonatomic, retain) NSSet *children;
@property (nonatomic, retain) id parent;
@end

@interface Library (CoreDataGeneratedAccessors)
- (void)addProjectsObject:(Project *)value;
- (void)removeProjectsObject:(Project *)value;
- (void)addProjects:(NSSet *)value;
- (void)removeProjects:(NSSet *)value;
- (id)parent;
- (void)setParent;
@end

and Library.m

#include "Library.h" 

@implementation Library

@dynamic title;
@dynamic projects;

- (NSSet*) children {
    [self willAccessValueForKey:@"children"];
    NSSet *set = [self valueForKey:@"projects"];
    [self didAccessValueForKey:@"children"];
    return set;
}

- (void) setChildren:(NSSet*)children {
    [self willChangeValueForKey:@"children"];
    [self setValue:children forKey:@"projects"];
    [self didChangeValueForKey:@"children"];
}

- (id)parent {
    [self willAccessValueForKey:@"parent"];
    [self didAccessValueForKey:@"parent"];
    return nil;
}

- (void)setParent:(id)parent {
    // Proposed parent value is ignored. Libraries have no parent.
    [self willChangeValueForKey:@"parent"];
    [self didChangeValueForKey:@"parent"];
}

@end
  1. Should children and parent be properties in the header file?

  2. Is this the suggested implementation? Should I also include addChildrenObject:, removeChildrenObject: , addChildren:, and removeChildren:? And implement them? (Same goes for the parent methods.)

  3. I assume that children doesn’t appear at all in the Core Data model. Is that right? How are the reverse relationships inferred then?

  4. Should I call [self willChangeValueForKey:@"children"] in setChildren: so children is KVO compliant? (Same for the other accessors.)

  5. In page 41 M. Zarra recommends to implement NSOutlineDataSource instead of using NSTreeController due to “results [that] can be unexpected and unclear”. Does anyone know what are those limitations?

  6. Finally, if I implement NSOutlineDataSource, would you recommend caching the fetch for the root objects? And if so, what is the proper way to maintain this cached array in sync with Core Data?

Thank you.

Best regards,

Jorge

+1  A: 

I don't think that the current version of NSTreeController requires attributes actually named "children" and "parent". You can use setChildrenKeyPath: to set the child path to any attribute name (as long as the attribute implements a parent-child hierarchy.) I'm pretty sure that is a rather old requirement. (Take that with a grain of salt, I haven't used the tree controller in sometime. See below)

As for your other questions:

(1) Yes

(2) Yes, yes and yes

(3) The relationships are maintained in the true attributes e.g. property. The entity/object graph is separate from the virtual graph that parent-child properties virtualize. Since the parent and child attributes do not actually have values, any change in the real values is immediately reflected in their return and vice versa. In short, you don't have to worry about them.

(4) Yes, the tree controller will be observing the virtual properties and not the real ones. If the virtual properties are not KVO compliant the controller will not work.

(5) Historically NSTreeController has been considered buggy. It has been around since IIRC 2004 and it never really worked well. A lot of old hands just ignore it completely. I haven't used it in some time.

(6) You generally only cache data that is not expected to change much. If the fetch is used to actually update the model or something else is expected to update the model e.g. a URL request, then you should not use a cache.

TechZen
Thank you very much for your answer.I didn't mean that they had to be named children, but that the name of the relationship had to be the same for all the objects in the hierarchy, while I prefer to have meaningful names for my relationships.Also, referring to 6), I don't thing that the top level will change very often, but what should I do when it does?
Jorge Ortiz
That makes more sense. As for (6) I slipped up and was thinking about iOS's NSFetchedResults controller. With a normal fetch, you have to rerun the fetch to update the returned array.
TechZen
Sorry, but after several attempts, something must be wrong. When I create a project, NSOutlineView fails to update the interface. If I quit the app (thus, saving the data) and run it again the project is there. If I recreate the model with the relationships "properly named" instead of using the virtual accessors it runs fine without changing the bindings. More help?
Jorge Ortiz
Well, if the tree controller is observing the virtual properties but you are changing the concrete properties somewhere else, then the controller will not receive the KVO notification. Try adding a custom accessor for one of the concrete properties that calls `didChangeValueForKey:` for one of the virtual properties and see if that clears it up.
TechZen
You are right, the problem is KVO notification. However, having to write custom accessors (and methods) for the real properties, didn't completely work and added a lot of complexity to the code. I like the dependecy method better. Thank you, anyhow.
Jorge Ortiz
+1  A: 

The problem I see here is that, while setting the "children" property will trigger KVO for the "projects" property, the reverse is not true. So, if you add a project to the library object via the "projects" relationship, the outline view will not update because it will not see any change in the "children" property.

The easiest way to enable this is to implement a method like so:

+ (NSSet*)keyPathsForValuesAffectingChildren
{
    return [NSSet setWithObject:@"projects"];
}

That should make any changes to the "projects" property also trigger a KVO notification for "children" as well.

On a side note, since the "children" property isn't part of your Core Data model, I don't think the will/didAccessValueForKey: calls are strictly necessary, though I don't think they'll hurt anything. Also, if you implement the method I mentioned above, you should no longer need to call will/didChangeValueForKey: in the setChildren: method, as Cocoa should automatically trigger the KVO for that when the "projects" key is changed.

Brian Webster
Yes! That makes the trick. Thank you. Does this mean that I should add dependencies for parent and children AND write my own accessors to them (not the real relationships) notifying of the access/change to both the virtual and real properties?
Jorge Ortiz