即使定义了依赖键,我是否也必须手动更新瞬态属性?

发布于 2024-10-10 04:22:19 字数 3480 浏览 0 评论 0原文

我开始使用瞬态属性一点不久前,我以为我很好地理解了它们,但是这个的行为并不像我想象的那样。它在数据模型中被定义为具有未定义类型的瞬态属性,并被声明为属性:

@property (nonatomic, readonly) NSSet * manufacturers;

该属性被声明为依赖于该对象上的另一个 1:M 关系:

+ (NSSet *) keyPathsForValuesAffectingManufacturers
{
    return [NSSet setWithObject:@"lines"];
}

我已经为该瞬态实现了一个相当标准的 getter属性:

- (NSSet *) manufacturers
{
    [self willAccessValueForKey:@"manufacturers"];
    NSSet *mfrs = [self primitiveManufacturers];
    [self didAccessValueForKey:@"manufacturers"];

    if (mfrs == nil)
    {
        NSMutableSet *working = [NSMutableSet set];

        /* rebuild the set of manufacturers into 'working' here */

        mfrs = working;
        [self setPrimitiveManufacturers:mfrs];
    }

    return mfrs;
}

不起作用的部分是,当从 lines 关系中添加/删除对象时,manufacturers 的缓存原始值似乎不会被清除。这个物体。当您添加/删除行时,肯定会调用manufacturers访问器,但是[selfprimitiveManufacturers]仍然返回旧的缓存值(这显然是非零),因此代码没有调用重建当前的制造商集,并且我最终得到了过时的返回值。

我是否误解了缓存瞬态属性值应该如何工作?或者,当通过某种 KVO 回调更改行时,我是否应该手动使缓存值无效,以便下次访问 manufacturers 时执行“if”内的更新代码?

非常感谢任何帮助。

编辑:我知道我基本上是在模拟与此瞬态属性的 1:M 关系(即瞬态关系,因为需要更好的词),并且我我还知道 文档警告不要使用 setPrimitiveValue:forKey: 来建立关系:

您通常应该使用此方法 仅修改属性(通常 短暂的),而不是关系。如果你 尝试设置一对多关系 新的 NSMutableSet 对象,它将 (最终)失败。在不寻常的 您需要修改的事件 使用这种方法的关系,你 首先使用现有的集合 PrimitiveValueForKey:(确保 方法不返回 nil),创建一个 可变副本,然后修改该副本

但是,由于我没有在真实 1:M 关系上设置原始值,而只是在模拟瞬态关系上设置原始值,因此我假设此警告不适用于我的用例。

编辑 #2: 因为我只是尝试跟踪何时从 lines 集中添加或删除对象(这是 manufacturers< 的预期值的唯一方法) /code> 可以更改),而不是尝试跟踪 lines 集中已有对象的属性更新,我认为我不需要像这样重量级的东西:https://github.com/mbrugger/CoreDataDependentProperties

解决方案:回答如下,我实现了以下解决方案,以在更新lines时清空manufacturers集。任何人都可以看到这方面的任何问题,或者建议任何更有效的实施方法吗?此操作必须快速,这一点很重要,因为它是在主线程中完成的,并且会明显延迟与 line 更新完成同时发生的 UI 更新。

- (void) awakeFromFetch
{
    [super awakeFromFetch];
    [self addObserver:self forKeyPath:@"lines" options:0 context:nil];
}


- (void) awakeFromInsert
{
    [super awakeFromInsert];
    [self addObserver:self forKeyPath:@"lines" options:0 context:nil];
}


- (void) didTurnIntoFault
{
    [self removeObserver:self forKeyPath:@"lines"];
    [super didTurnIntoFault];
}


- (void) observeValueForKeyPath:(NSString *)keyPath
                       ofObject:(id)object
                         change:(NSDictionary *)change
                        context:(void *)context
{
    if ([object isEqual:self] && [keyPath isEqualToString:@"lines"])
    {
        [self setPrimitiveManufacturer:nil];
    }
    else
    {
        [super observeValueForKeyPath:keyPath 
                             ofObject:object 
                               change:change 
                              context:context];
    }
}

I started working with transient properties a little while ago, and thought I understood them pretty well, but this one isn't behaving how I thought it would. It is defined in the data model as a transient property with undefined type, and is declared as a property thus:

@property (nonatomic, readonly) NSSet * manufacturers;

This property is declared to be dependent on another 1:M relationship on this object:

+ (NSSet *) keyPathsForValuesAffectingManufacturers
{
    return [NSSet setWithObject:@"lines"];
}

I have implemented a fairly standard getter for this transient property:

- (NSSet *) manufacturers
{
    [self willAccessValueForKey:@"manufacturers"];
    NSSet *mfrs = [self primitiveManufacturers];
    [self didAccessValueForKey:@"manufacturers"];

    if (mfrs == nil)
    {
        NSMutableSet *working = [NSMutableSet set];

        /* rebuild the set of manufacturers into 'working' here */

        mfrs = working;
        [self setPrimitiveManufacturers:mfrs];
    }

    return mfrs;
}

The part that isn't working is that the cached primitive value for manufacturers doesn't appear to be wiped out when objects are added / removed from the lines relationship on this object. The manufacturers accessor is definitely called when you add / remove lines, but [self primitiveManufacturers] still returns the old cached value (which is obviously non-nil), so the code to rebuild the current set of manufacturers isn't called and I end up with an out-of-date return value.

Am I misunderstanding something about how cached transient property values are supposed to work? Or am I supposed to manually nullify the cached value when lines are changed through some kind of KVO callback so that the update code inside the "if" is executed the next time manufacturers is accessed?

Any help much appreciated.

Edit: I am aware that I'm basically simulating a 1:M relationship with this transient property (i.e. a transient relationship, for want of a better word), and I'm also aware that the documentation warns against using setPrimitiveValue:forKey: for relationships:

You should typically use this method
only to modify attributes (usually
transient), not relationships. If you
try to set a to-many relationship to a
new NSMutableSet object, it will
(eventually) fail. In the unusual
event that you need to modify a
relationship using this method, you
first get the existing set using
primitiveValueForKey: (ensure the
method does not return nil), create a
mutable copy, and then modify the copy

However, since I am not setting a primitive value on a real 1:M relationship, only my simulated transient one, I had assumed that this warning did not apply to my use case.

Edit #2: Since I am only trying to track when objects are added or removed from the lines set (this is the only way the expected value of manufacturers can change), as opposed to trying to track updates to attributes on the objects that are already in the lines set, I don't think I should need anything as heavyweight as this: https://github.com/mbrugger/CoreDataDependentProperties

Solution: In light of the answer below, I implemented the following solution to blank out the manufacturers set when lines is updated. Can anyone see any issues with this, or suggest any more efficient ways of implementing it? It is important that this operation be fast because it is done in the main thread and will visibly delay UI updates that happen at the same time as the line updates are done.

- (void) awakeFromFetch
{
    [super awakeFromFetch];
    [self addObserver:self forKeyPath:@"lines" options:0 context:nil];
}


- (void) awakeFromInsert
{
    [super awakeFromInsert];
    [self addObserver:self forKeyPath:@"lines" options:0 context:nil];
}


- (void) didTurnIntoFault
{
    [self removeObserver:self forKeyPath:@"lines"];
    [super didTurnIntoFault];
}


- (void) observeValueForKeyPath:(NSString *)keyPath
                       ofObject:(id)object
                         change:(NSDictionary *)change
                        context:(void *)context
{
    if ([object isEqual:self] && [keyPath isEqualToString:@"lines"])
    {
        [self setPrimitiveManufacturer:nil];
    }
    else
    {
        [super observeValueForKeyPath:keyPath 
                             ofObject:object 
                               change:change 
                              context:context];
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

蘸点软妹酱 2024-10-17 04:22:19

keyPathsForValuesAffectingManufacturers并不意味着当线路改变时制造商的值会被删除,而是意味着当线路改变时观察制造商的对象应该得到通知,因为改变线路会自动改变制造商而不会触发通知。当一个属性基于另一个属性获取其值时使用此属性。针对你的情况,换线的时候需要更换厂家。

keyPathsForValuesAffectingManufacturers does not mean that the value of manufacturers will be deleted when lines is changed, it means that objects observing manufacturers should be notified when lines changes, because changing lines automatically changes manufacturers without triggering notifications. This is used when one property gets its value based on another. For your situation, you need to change manufacturers when you change lines.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文