Cocoa NSOutlineView、NSTreeController和异构层次结构

Cocoa NSOutlineView、NSTreeController和异构层次结构,cocoa,core-data,cocoa-bindings,Cocoa,Core Data,Cocoa Bindings,在Marcus Zarra的核心数据手册的第40页中,他建议,由于NSTreeController需要相同的键来访问层次结构中的所有对象(例如,子对象),这可能意味着不太有意义的关系名称,因此可以为所需的关系编写额外的访问器。我认为这是一个好主意,但我不确定如何实施 让我以Aperture的数据模型为例:可以有许多库,每个库可以有许多项目,每个库可以有许多照片。所以,如果我分别命名实体库、项目和照片以及它们之间的关系projects、photos和nothing,那么以下是库的正确实现吗 图书馆

在Marcus Zarra的核心数据手册的第40页中,他建议,由于NSTreeController需要相同的键来访问层次结构中的所有对象(例如,子对象),这可能意味着不太有意义的关系名称,因此可以为所需的关系编写额外的访问器。我认为这是一个好主意,但我不确定如何实施

让我以Aperture的数据模型为例:可以有许多库,每个库可以有许多项目,每个库可以有许多照片。所以,如果我分别命名实体库、项目和照片以及它们之间的关系projects、photos和nothing,那么以下是库的正确实现吗

图书馆

@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
和图书馆

#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
  • 子项和父项应该是头文件中的属性吗

  • 这是建议的实施方式吗?我还应该包括
    addChildrenObject:
    removeChildrenObject:
    addChildren:
    ,和
    removeChildren:
    ?并实施它们?(父方法也是如此。)

  • 我假设在核心数据模型中根本没有出现子对象。是这样吗?那么,反向关系是如何推断的呢

  • 我是否应该在setChildren中调用[self-willChangeValueForKey:@“children”:那么children是KVO兼容的?(其他访问者也是如此。)

  • 在第41页中,M.Zarra建议实施NSOutlineDataSource,而不是使用NSTreeController,因为“结果可能是意外的和不清楚的”。有人知道这些限制是什么吗

  • 最后,如果我实现NSOutlineDataSource,您是否建议缓存根对象的获取?如果是这样的话,什么是保持缓存阵列与核心数据同步的正确方法

  • 多谢各位

    致以最良好的祝愿


    Jorge

    我认为当前版本的NSTreeController不需要实际命名为“children”和“parent”的属性。您可以使用
    setChildrenKeyPath:
    将子路径设置为任何属性名称(只要该属性实现父子层次结构)。我很确定这是一个相当古老的要求。(恕我直言,我已经有一段时间没有使用树控制器了。见下文)

    至于你的其他问题:

    (1) 对

    (2) 对,对,对

    (3) 关系保存在真实属性中,例如属性。实体/对象图与父子属性虚拟化的虚拟图是分开的。由于父属性和子属性实际上没有值,因此实际值中的任何更改都会立即反映在它们的返回中,反之亦然。简言之,你不必担心他们

    (4) 是的,树控制器将观察虚拟属性,而不是真实属性。如果虚拟属性不符合KVO,则控制器将无法工作

    (5) 从历史上看,该控制器一直被认为是有缺陷的。自IIRC 2004年以来,它一直存在,但从未真正发挥过作用。很多老手完全忽视了这一点。我有一段时间没用了


    (6) 您通常只缓存预期不会有太大变化的数据。如果取数用于实际更新模型,或者需要其他东西来更新模型,例如URL请求,则不应使用缓存

    我在这里看到的问题是,虽然设置“children”属性将触发“projects”属性的KVO,但情况并非如此。因此,如果通过“项目”关系将项目添加到库对象中,大纲视图将不会更新,因为它不会看到“子对象”属性中的任何更改

    实现这一点的最简单方法是实现如下方法:

    + (NSSet*)keyPathsForValuesAffectingChildren
    {
        return [NSSet setWithObject:@"projects"];
    }
    
    这将使对“projects”属性的任何更改也会触发对“children”的KVO通知


    另一方面,由于“children”属性不是核心数据模型的一部分,我认为
    will/didAccessValueForKey:
    调用是绝对必要的,尽管我不认为它们会损害任何东西。另外,如果您实现了我上面提到的方法,您不需要再调用
    setChildren:
    方法中的
    will/didChangeValueForKey:
    ,因为当“projects”键更改时,Cocoa会自动触发KVO。

    非常感谢您的回答。我的意思不是说它们必须命名为children,而是层次结构中所有对象的关系名称必须相同,而我更喜欢为我的关系命名有意义的名称。另外,提到6),我不认为顶层会经常改变,但当它改变时,我应该怎么做?这更有意义。至于(6)我犯了一个错误,当时正在考虑iOS的NSFetchedResults控制器。对于正常的获取,您必须重新运行获取以更新返回的数组。很抱歉,在多次尝试之后,一定是出了问题。创建项目时,NSOutlineView无法更新接口。如果我退出应用程序(从而保存数据)并再次运行它,项目就在那里。如果我用“正确命名”的关系重新创建模型,而不是使用虚拟访问器,那么它在不更改绑定的情况下运行良好。更多帮助?如果树控制器正在观察虚拟属性,但您正在其他地方更改具体属性,那么控制器将不会收到KVO通知。尝试为一个具体属性添加自定义访问器,该属性调用其中一个虚拟属性的
    didChangeValueForKey:
    ,看看这是否会清除它。您是对的,问题是KVO通知。然而,必须为实际属性编写自定义访问器(和方法),这并不完全有效,并且给代码增加了很多复杂性。我更喜欢依赖法。无论如何,谢谢你。是的!这就是诀窍。非常感谢。